JEP-538 introduces the third preview of the PEM API. This API provides native support for decoding and encoding cryptographic objects in the Privacy-Enhanced-Mail (PEM) format, which is kind of an industry standard.
To better understand the benefits this brings to the Java ecosystem, it is important to first look at the current state of things and the limitations. I will use an example to illustrate this.
Example
The entire example is available on GitHub (please keep in mind that this is strictly for educational purposes, which is why the passwords are hardcoded). For this setup, I used the OpenJDK Runtime Environment (build 27-ea+25-2247).
In this example, I am setting up an Nginx server in a Docker container with mutual TLS (mTLS) configured. The goal is to send a request to this server from a Java application. To achieve this, we essentially need to obtain an SSLContext object instance.
We will start with the assumption that we already have the following files:
Current landscape
In the traditional workflow, before we can even use our certificate and private key within the application, we are forced to pre-create keystore and truststore bundles. While this might not seem like an overly complicated task at first glance, it introduces an extra layer of complexity to our deployment pipeline.
Furthermore, it forces us to rely on external tools - requiring OpenSSL for PKCS12 bundles, or a combination of both OpenSSL and keytool for JKS bundles. Let's look at how this traditional approach is typically implemented.
To obtain a SSLContext instance, we generally need to get KeyManager and TrustManager instances. One way to acquire them is by providing keystore files in one of two formats:
- PKCS12 - The default keystore format since JDK9 (introduced with JEP-229).This is a widely used industry standard for keystores.
- JKS - Java KeyStore, which is a proprietary format specific to Java applications.
To bundle our client certificate and private key into a PKCS12 file – which will be used to create the KeyManager instance – we can utilize the OpenSSL command-line tool with the following command:
Here, we have essentially defined:
- The input certificate and the private key, along with the password required to decrypt it.
- The output file name.
- The entry alias (although there is only a single entry inside the bundle in this example, a bundle can contain multiple entries).
- The password to protect the bundle file.
Next, we need to create a separate bundle to be used by the TrustManager. This task can be handled by the Java keytool utility. We can use it directly in this case since we only need to import a Certificate Authority (CA) certificate into the bundle:
This command:
- takes a certificate as input,
- assigns an alias to this entry,
- defines an output file with the appropriate type and password.
With both files now in place, we can finally use them in our application. There are two ways to approach this:
- Create custom SSLContext like BundleBasedSSLContextProvider
- Use default SSLContext by overriding the system properties responsible for configuring the keystore and truststore, such as:
If you are forced to use the JKS format for some reason, there is an additional step to perform. Because keytool cannot directly bundle a PEM private key and a PEM certificate into a JKS file, you must first bundle them into a PKCS12 file using the OpenSSL utility, as shown previously. Once that is done, you can convert the PKCS12 bundle into a JKS bundle using the following command:
Here, we simply define:
- the input file, along with its type and password,
- the output file, along with its type and password.
After running this, we can obtain the SSLContext just as before. If you want to do this via the default SSLContext, it is crucial to properly configure the relevant system properties.
Use the Bouncy Castle library
Another option is to add a dependency to the Bouncy Castle library, which offers broad support for cryptography and cryptographic protocols. With this approach, we do not need to create bundle files at all. Instead, we can parse the PEM files programmatically. You can find an example of this in the BouncyCastleContextProvider class.
The main takeaway here is that we must first register a custom BouncyCastleProvider security provider. After that, we can use it to decode our PEM files directly:
It also provides capability to decrypt private key easily:
While this approach is much cleaner, it still requires adding an external dependency to the project. As is often the case, bringing in third-party libraries can introduce maintenance overhead and potential compatibility issues down the road when upgrading other libraries or the JDK itself.
Writing a custom implementation by hand
It is also possible to write your own custom implementation for parsing PEM files. However, this is generally not recommended. Because of the vast number of file formats and cryptographic algorithms that need to be supported, maintaining and extending a custom parser would quickly become tedious and difficult to manage.
Custom solutions
Some popular frameworks and libraries have introduced their own built-in support for PEM files. For example:
- Kafka via KIP-651
- Netty via SSLContextBuilder
- SpringBoot via SSL Bundles
How does JEP-538 change the landscape?
As shown above, all existing solutions come with certain limitations. They typically require you to:
- Bundle PEM files inside a PKCS12 or JKS keystore.
- Add an additional external dependency to your project.
- Rely on framework-specific utilities or maintain custom parsing logic.
With JEP-538, we get a native PEM API that allows decoding and encoding PEM files directly without any additional hassle. Decoding certificates and private keys becomes as simple as this:
With just a few lines of code, we can obtain everything needed to configure a TLS connection for our application. The complete code snippet can be found here: PEMBasedContextProvider.
Conclusion
In my opinion, the PEM API is a highly valuable addition that will significantly improve the developer experience by dramatically simplifying how we work with PEM files. If you want to try it out yourself, you can find the complete source code used in the examples above in this GitHub repository.
Reviewed by Michal Ostruszka




