JEP 470 in Java 25

  • Last Updated: October 23, 2025
  • By: javahandson
  • Series
img

JEP 470 in Java 25

JEP 470 in Java 25 introduces native APIs for PEM-encoded cryptographic objects, letting developers securely parse keys, certificates, and CSRs without BouncyCastle. Learn how to migrate existing PEM handling code to Java 25 using PEMDecoder and PEMEncoder for cleaner, safer, and dependency-free encryption workflows.

1. JEP 470 at a Glance

JEP 470 makes it straightforward for Java to read and write PEM files—the text-based files commonly used for cryptographic keys and certificates, recognisable by headers like
-----BEGIN PUBLIC KEY-----.

PEM is essentially a human-readable wrapper around binary key or certificate data. With Java 25 (preview), the JDK can now directly convert PEM text into Java objects, such as PublicKey, PrivateKey, and Certificate, and also write those objects back to PEM—without any third-party libraries.

Earlier, working with PEM in Java was cumbersome. Developers had to manually strip the BEGIN/END lines, Base64-decode the content, and then pass the bytes to KeyFactory or CertificateFactory, or rely on external libraries like BouncyCastle. JEP 470 eliminates all that boilerplate: we simply give Java the PEM text, and it returns the correct cryptographic object.

Why this matters: less code, fewer mistakes, and smoother interoperability with tools like OpenSSL and Let’s Encrypt. For students or developers new to security, it makes working with real-world keys and certificates in plain Java significantly easier.

2. PEM vs DER

DER is the compact binary encoding used to store keys and certificates (the actual ASN.1 data). It’s efficient for machines and storage, but unreadable to humans—open a DER file in a text editor, and you’ll see random binary data.

PEM is simply a text wrapper around DER. The same binary data is Base64-encoded and surrounded by markers like
-----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----.
Because it’s plain text, PEM is easy to copy, paste, and store in configs, CI secrets, or emails.

2.1. File extensions can be misleading

Don’t rely only on file extensions.

  • DER commonly uses .der, sometimes .cer or .crt
  • PEM commonly uses .pem, but also .crt, .cer, and .key

Rule of thumb:

  • If the file contains BEGIN/END lines and Base64 → PEM
  • If it’s raw binary → DER

2.2. When should we use which?

Use PEM when humans need to handle the file, or when working with tools like OpenSSL, Let’s Encrypt, or cloud KMS systems.

Use DER when an API or device explicitly requires binary input, or when you want the most compact format for storage or transport.

Many systems support both—always follow what the documentation specifies.

2.3. Watch out for PEM labels and formats

PEM files look similar but can represent different encodings. Common modern forms are:

-----BEGIN CERTIFICATE-----
-----BEGIN PUBLIC KEY-----      (SPKI)
-----BEGIN PRIVATE KEY-----     (PKCS#8)

Older, RSA-specific formats use:

-----BEGIN RSA PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY----- (PKCS#1)

These formats are not interchangeable. Treating a PKCS#1 key as PKCS#8 (or vice versa) will cause parsing errors.
If something fails to load, the first thing to check is the PEM label.

Example: A PEM public key (text)

-----BEGIN PUBLIC KEY-----
<br>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnMLttZ9oYz+RhFbG1Gk3
<br>hQGzhM06VNoy/7XH/kk4Qx0ihmVk0MR0mHhGZcJDnKmFNs+dnD66O7K2OPhlm/xG
<br>5tsVMCgDnxZVtJhWytS6T9oR0c7sX7L6tXSaG6oSKY+UFWrZ/8XvN4XhT4Vddc1k
<br>vUHr5WJgOkQHuDrMZmZr4h8fMwBhH+oYX9YAmEZz3r+M4R64GHwKCh4xRRqGx1fV
<br>nWvXGcLuJDm0A1ukr35DJl1umzVN8m3r2mZqJ8s4t3JpDqE9hzld/h7dLOr5U0o5
<br>hzMZd0mPKtsRoc0M0h7HqT5zV2UEoLfd9nYaTy9PK+zEJ8rNx2oUVZSwZONsFL93
<br>UwIDAQAB
<br>-----END PUBLIC KEY-----

2.4. Converting PEM and DER with OpenSSL

DER → PEM

openssl x509 -inform DER -in cert.der -out cert.pem

PEM → DER

openssl x509 -outform DER -in cert.pem -out cert.der

Convert old PKCS#1 RSA private key to PKCS#8 (recommended)

openssl pkcs8 -topk8 -nocrypt -in rsa_key.pem -out pkcs8_key.pem

3. Before vs After JEP 470

3.1. Before JEP 470

Working with PEM in Java was unnecessarily fussy. Core JDK APIs expect DER (binary) input, while most real-world keys and certificates from OpenSSL, Let’s Encrypt, or cloud KMS are PEM (Base64 text with BEGIN/END markers).

Developers had to:

  • Read PEM as text
  • Strip headers and footers
  • Remove line breaks
  • Base64-decode to DER
  • Feed the bytes into KeyFactory or CertificateFactory

To avoid this boilerplate, many teams added libraries like BouncyCastle. Even then, small mistakes—extra whitespace, wrong labels, or mixing PKCS#1 and PKCS#8—often resulted in confusing parsing errors.

public-key.pem

-----BEGIN PUBLIC KEY-----<br>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnMLttZ9oYz+RhFbG1Gk3<br>hQGzhM06VNoy/7XH/kk4Qx0ihmVk0MR0mHhGZcJDnKmFNs+dnD66O7K2OPhlm/xG<br>5tsVMCgDnxZVtJhWytS6T9oR0c7sX7L6tXSaG6oSKY+UFWrZ/8XvN4XhT4Vddc1k<br>vUHr5WJgOkQHuDrMZmZr4h8fMwBhH+oYX9YAmEZz3r+M4R64GHwKCh4xRRqGx1fV<br>nWvXGcLuJDm0A1ukr35DJl1umzVN8m3r2mZqJ8s4t3JpDqE9hzld/h7dLOr5U0o5<br>hzMZd0mPKtsRoc0M0h7HqT5zV2UEoLfd9nYaTy9PK+zEJ8rNx2oUVZSwZONsFL93<br>UwIDAQAB<br>-----END PUBLIC KEY-----

Using Java 21

package com.javahandson;

import java.io.IOException;
import java.nio.file.*;
import java.security.*;
import java.security.spec.*;
import java.util.Base64;

public class Test {

    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

        String pem = Files.readString(Path.of("com/javahandson/public-key.pem"));

        pem = pem.replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s", ""); // remove newlines & spaces

        byte[] derBytes = Base64.getDecoder().decode(pem);

        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derBytes);

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        System.out.println("Algorithm: " + publicKey.getAlgorithm());
        System.out.println("Format: " + publicKey.getFormat());
    }
}
Output: Algorithm: RSA
Format: X.509

3.2. After JEP 470

With JEP 470, Java gains native, first-class support for PEM. We can now give the JDK PEM text directly and receive proper Java objects—PublicKey, PrivateKey, Certificate, and more—without manual cleanup or third-party helpers.

In short, Java finally speaks the same file-format language as the tools we already use, making everyday key and certificate handling far simpler.

How the flow changes

Earlier pipeline:

PEM text
→ string cleanup
→ Base64 decode
→ DER bytes
→ KeyFactory / CertificateFactory

With JEP 470:

PEM text
→ JDK decode
→ ready-to-use key or certificate

The new flow is shorter, clearer, and far less error-prone—especially valuable in application code and CI/CD pipelines that import or rotate keys and certificates.

Using Java 25

package com.javahandson;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.PEMDecoder;   // NEW (preview)
import java.security.PublicKey;   // existing

public class Test {

    public static void main(String[] args) throws Exception {
        // Read PEM text (UTF-8 is fine)
        String pem = Files.readString(Path.of("com/javahandson/public-key.pem"), StandardCharsets.UTF_8);

        // Decode PEM → PublicKey
        PublicKey pub = PEMDecoder.of().decode(pem, PublicKey.class);
        System.out.println("Algorithm: " + pub.getAlgorithm());
    }
}
Output: Algorithm: RSA

3.3. Important boundaries still apply

JEP 470 removes boilerplate, not structural rules. Formats and labels must still match:

  • PUBLIC KEY (SPKI) ≠ RSA PUBLIC KEY (PKCS#1)
  • PRIVATE KEY (PKCS#8) ≠ RSA PRIVATE KEY (PKCS#1)

If parsing fails, the first thing to check is the PEM label. When possible, convert legacy PKCS#1 keys to modern PKCS#8.

We write less code, avoid extra dependencies, and get smoother interoperability with the broader ecosystem. Since this feature is previewed in Java 25, compilation and runtime require preview flags. As it stabilises, this is likely to become the default, no-drama way to handle PEM in plain Java.

4. Core API & usage

This section demonstrates the practical method for reading and writing PEM using the JDK itself. In Java 25 (preview), the new PEM APIs (such as PEMDecoder and PEMEncoder) allow us to convert PEM text directly into Java objects—PublicKey, PrivateKey, and Certificate—and encode them back to PEM just as easily.

There’s no need to strip markers or perform Base64 decoding manually. We simply hand the PEM content to the JDK and get usable cryptographic objects in return.

Since this is a preview feature, remember to compile and run with --enable-preview.

4.1. Setup (Java 25, preview)

Compile/run with preview enabled:

javac --release 25 --enable-preview Test.java
java --enable-preview Test

4.2. Read a public key from PEM (Java 25, preview)

package com.javahandson;

import java.nio.file.Files;
import java.nio.file.Path;
import java.security.PEMDecoder;   // NEW (preview)
import java.security.PublicKey;   // existing

public class Test {
    static void main() throws Exception {   
        String pem = Files.readString(Path.of("com/javahandson/public-key.pem")); 
        PublicKey publicKey = PEMDecoder.of().decode(pem, PublicKey.class);
        System.out.println("Public key algorithm : "+publicKey.getAlgorithm());
    }
}
Output: Public key algorithm : RSA

4.3. Read a private key from PEM

private-key.pem

-----BEGIN RSA PRIVATE KEY-----<br>MIIBOQIBAAJAXWRPQyGlEY+SXz8Uslhe+MLjTgWd8lf/nA0hgCm9JFKC1tq1S73c<br>Q9naClNXsMqY7pwPt1bSY8jYRqHHbdoUvwIDAQABAkAfJkz1pCwtfkig8iZSEf2j<br>VUWBiYgUA9vizdJlsAZBLceLrdk8RZF2YOYCWHrpUtZVea37dzZJe99Dr53K0UZx<br>AiEAtyHQBGoCVHfzPM//a+4tv2ba3tx9at+3uzGR86YNMzcCIQCCjWHcLW/+sQTW<br>OXeXRrtxqHPp28ir8AVYuNX0nT1+uQIgJm158PMtufvRlpkux78a6mby1oD98Ecx<br>jp5AOhhF/NECICyHsQN69CJ5mt6/R01wMOt5u9/eubn76rbyhPgk0h7xAiEAjn6m<br>EmLwkIYD9VnZfp9+2UoWSh0qZiTIHyNwFpJH78o=<br>-----END RSA PRIVATE KEY-----
package com.javahandson;

import java.nio.file.Files;
import java.nio.file.Path;
import java.security.PEMDecoder;
import java.security.PrivateKey;

public class Test {
    static void main() throws Exception {
        String pem = Files.readString(Path.of("com/javahandson/private-key.pem"));
        PrivateKey pk = PEMDecoder.of().decode(pem, PrivateKey.class);
        System.out.println("Private key algorithm : "+pk.getAlgorithm());
    }
}
Output: Private key algorithm : RSA

4.4. Read an X.509 certificate (and a chain) from PEM

cert.pem

-----BEGIN CERTIFICATE-----<br>MIICMzCCAZygAwIBAgIJALiPnVsvq8dsMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV<br>BAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNVBAcTA2ZvbzEMMAoGA1UEChMDZm9v<br>MQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2ZvbzAeFw0xMzAzMTkxNTQwMTlaFw0x<br>ODAzMTgxNTQwMTlaMFMxCzAJBgNVBAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNV<br>BAcTA2ZvbzEMMAoGA1UEChMDZm9vMQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2Zv<br>bzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzdGfxi9CNbMf1UUcvDQh7MYB<br>OveIHyc0E0KIbhjK5FkCBU4CiZrbfHagaW7ZEcN0tt3EvpbOMxxc/ZQU2WN/s/wP<br>xph0pSfsfFsTKM4RhTWD2v4fgk+xZiKd1p0+L4hTtpwnEw0uXRVd0ki6muwV5y/P<br>+5FHUeldq+pgTcgzuK8CAwEAAaMPMA0wCwYDVR0PBAQDAgLkMA0GCSqGSIb3DQEB<br>BQUAA4GBAJiDAAtY0mQQeuxWdzLRzXmjvdSuL9GoyT3BF/jSnpxz5/58dba8pWen<br>v3pj4P3w5DoOso0rzkZy2jEsEitlVM2mLSbQpMM+MUVQCQoiG6W9xuCFuxSrwPIS<br>pAqEAuV4DNoxQKKWmhVv+J0ptMWD25Pnpxeq5sXzghfJnslJlQND<br>-----END CERTIFICATE-----
package com.javahandson;

import java.nio.file.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.util.regex.*;

public class Test {
    
    private static final Pattern CERT_BLOCK =
            Pattern.compile("-----BEGIN CERTIFICATE-----[\\s\\S]*?-----END CERTIFICATE-----");

    static void main() throws Exception {

        String pem = Files.readString(Path.of("com/javahandson/cert.pem"));

        PEMDecoder decoder = PEMDecoder.of(); // JDK 25 preview
        List<X509Certificate> chain = new ArrayList<>();

        Matcher m = CERT_BLOCK.matcher(pem);
        while (m.find()) {
            String block = m.group();
            X509Certificate cert = decoder.decode(block, X509Certificate.class);
            chain.add(cert);
        }

        for (X509Certificate x : chain) {
            System.out.println(
                    "Subject=" + x.getSubjectX500Principal() +
                            " | Issuer=" + x.getIssuerX500Principal());
        }
    }
}
Output: Subject=CN=foo, OU=foo, O=foo, L=foo, ST=foo, C=US | Issuer=CN=foo, OU=foo, O=foo, L=foo, ST=foo, C=US

4.5. Encode back to PEM (keys/certs → text)

Generate a new public-private key pair and store the public key in a file.

package com.javahandson;

import java.nio.file.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.util.regex.*;

public class Test {
    static void main() throws Exception {

        KeyPair kp = KeyPairGenerator.getInstance("RSA").generateKeyPair();

        // (Minimal manual fallback if you want to show the format)
        String pem = "-----BEGIN PUBLIC KEY-----\n" +
             Base64.getMimeEncoder(64, "\n".getBytes()).encodeToString(kp.getPublic().getEncoded()) +
             "\n-----END PUBLIC KEY-----\n";

        Files.writeString(Path.of("com/javahandson/new-public-key.pem"), pem);
    }
}
Output: A new file is generated

new-public-key.pem

-----BEGIN PUBLIC KEY-----<br>MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyAAq3gUiYxzvkrQLNVVk<br>AG8J8QMI9sqaald6qqUX3nkxDeDcjS064Ah+8tegOnKuaI67kBWyYoF/hD/6Hi1A<br>TuX7cJeU0rqp8rgpkrByQ/8V4OtX/t3lYyUSwyC/+DobsuiLnosFejzaIzdsGgoC<br>1kQdTSdsRVeJIMBcW/Ul4KN70lWeH41kfALRG6IiQsMOlCoRA1dVYA7SkqPp3HBi<br>tf/EVFiRuye8r8Al89hswZDLl24/WG4SY/UIXBdBrLY7YWqkTqmcC04IWFIySbNJ<br>tBtZ1rcUx6wJqzoX7Utl/VC7s6ZwugEAC2TeW5dGJyTqlpK7LLOcFPAGpkf7KrgP<br>YEDspFu3Yd9V2K8ve6NnVARUwuN6g0Fmq14YoULq756Qwv5ky++UuZ5ijN9wS4BF<br>gvTe28veQdyIsVbeImTEBiruDfLPPBxpU+hOD2Bh+W6GnVaUzbrV26cZ5WSiE/e9<br>/TOQR8Iz0zqD3jXEWah7b2vvRN1Fin1dBC+aJKToFfOTAgMBAAE=<br>-----END PUBLIC KEY-----

5. Security & migration tips

Java 25’s native PEM support is more than a convenience feature—it’s a security improvement. For years, developers relied on hand-written PEM parsing logic or helper libraries to strip headers, normalise whitespace, and Base64-decode content. That approach was fragile and error-prone. JEP 470 removes this risk by providing a standardised, well-tested PEM handler inside the JDK.

Prefer JDK PEM APIs over custom parsers – Avoid manual parsing of BEGIN/END markers and Base64 decoding. The JDK’s PEM APIs validate structure, labels, and encoding for you, reducing the risk of malformed or malicious input. Delegating this work to the JDK eliminates an entire class of parsing bugs.

Keep cryptographic material inside the JDK – With native PEM support, decoding and object creation stay within Java’s audited cryptographic framework. This avoids passing sensitive key bytes through custom utilities and lowers the risk of accidental leaks or misuse.

Handle sensitive data carefully – Don’t log or print key material. Prefer streams and byte-based APIs over converting PEM content into Strings. Use try-with-resources and clear buffers promptly to prevent secrets from lingering in memory.

Always validate the PEM type – PEM blocks carry labels such as PRIVATE KEY, PUBLIC KEY, or CERTIFICATE. Ensure the label matches the expected use case before decoding. Mixing formats (for example, PKCS#1 vs PKCS#8) remains a common cause of failures.

Plan for mixed JDK environments – Since JEP 470 is previewed in Java 25, applications must run with --enable-preview. In environments using multiple JDK versions, introduce a small adapter layer that uses the new JDK API on Java 25+ and falls back to existing logic on older versions.

Clean up dependencies – Many projects depend on BouncyCastle solely for PEM handling. After migrating, keep such libraries only if you need advanced features like CMS or PKCS#7. Fewer dependencies mean a smaller attack surface and easier maintenance.

Migrate incrementally and test real inputs – Start by replacing only the PEM parsing logic. Test with real-world files—different newline styles, certificate chains, and PEMs from multiple tools—to ensure nothing breaks. The JDK handles most cases well, but validation is still essential.

6. Migrating from BouncyCastle to Java 25 PEM API

To see the practical impact of JEP 470, let’s compare how PEM-encoded keys were traditionally handled and how Java 25 simplifies the same workflow.

For years, most Java applications relied on BouncyCastle to work with PEM files. Classes like PEMParser and JcaPEMKeyConverter were used to decode Base64 content and convert it into usable PublicKey or PrivateKey objects. While reliable, this approach required a third-party dependency and additional parsing logic.

With Java 25 (preview), PEM support is now built directly into the JDK via the new PEMDecoder and PEMEncoder APIs. These APIs can parse and generate PEM-encoded keys and certificates safely, without manual processing or external libraries. This reduces dependencies and keeps sensitive cryptographic operations entirely within the JDK’s well-audited security framework.

Below are two small examples:

  • The first uses BouncyCastle to encrypt a simple string with an RSA public key.
  • The second shows the same logic rewritten for Java 25, using its built-in PEM API — eliminating the need for custom parsing or third-party libraries.

Ex. BouncyCastle version – encryption only

package com.javahandson;

import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;

import javax.crypto.Cipher;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.util.Base64;
public class Encrypt {

    static void main() throws Exception {
        PublicKey pub = loadPublicKeyWithBC(getPublicKey());
        byte[] ct = encryptRSAOAEPSHA256("Hello BC encrypt-only".getBytes(StandardCharsets.UTF_8), pub);
        System.out.println("Ciphertext (Base64): " + Base64.getEncoder().encodeToString(ct));

    }

    private static PublicKey loadPublicKeyWithBC(String pem) throws Exception {
        try (PEMParser parser = new PEMParser(new StringReader(pem))) {
            Object obj = parser.readObject();
            SubjectPublicKeyInfo spki = (SubjectPublicKeyInfo) obj;
            return new JcaPEMKeyConverter().getPublicKey(spki);
        }
    }

    private static byte[] encryptRSAOAEPSHA256(byte[] data, PublicKey key) throws Exception {
        Cipher c = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        c.init(Cipher.ENCRYPT_MODE, key);
        return c.doFinal(data);
    }

    private static String getPublicKey() {
        return "-----BEGIN PUBLIC KEY-----\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnMLttZ9oYz+RhFbG1Gk3\n" +
                "hQGzhM06VNoy/7XH/kk4Qx0ihmVk0MR0mHhGZcJDnKmFNs+dnD66O7K2OPhlm/xG\n" +
                "5tsVMCgDnxZVtJhWytS6T9oR0c7sX7L6tXSaG6oSKY+UFWrZ/8XvN4XhT4Vddc1k\n" +
                "vUHr5WJgOkQHuDrMZmZr4h8fMwBhH+oYX9YAmEZz3r+M4R64GHwKCh4xRRqGx1fV\n" +
                "nWvXGcLuJDm0A1ukr35DJl1umzVN8m3r2mZqJ8s4t3JpDqE9hzld/h7dLOr5U0o5\n" +
                "hzMZd0mPKtsRoc0M0h7HqT5zV2UEoLfd9nYaTy9PK+zEJ8rNx2oUVZSwZONsFL93\n" +
                "UwIDAQAB\n" +
                "-----END PUBLIC KEY-----\n";
    }
}
Output: Ciphertext (Base64): mVNbe87cXe0e8QXQJRuTvSZAGpxaVPE+y9banSu9v9U8v5klZkB9ECh4BheVeheQ2C8GHEuqhi1phX4KWdI3r3OQ1tcyWfVkomG45adeGsvvws+4OtvawE0jxzujLIOOVXXwfjy+0ghrnN2fvFh8AqqXPdf9nMkfkxyuoMXk7JhXRaK9YNwp5MEBMydlR16GFwbDPbYExIePQk4xbEdcE+xjR+SWFS/uBLRcT4E4MNAoL5bTmrWM+oXuEH/O4hM9BR/kMj7KTEd3jbsYGHVI2A+NelKan+Pgne/AbDpBkHeS4lGCH6mLC3xuCl7D5iNsu6vBKVH9xR8mD2+WHMrjIg==

Ex. Encryption only, Java 25 + JEP 470

package com.javahandson;

import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.util.Base64;

import javax.crypto.Cipher;

import java.security.PEMDecoder;   // preview API
import java.security.DEREncodable;

public class Encrypt {

    static void main() throws Exception {
        PublicKey publicKey = loadPublicKeyFromPem(getPublicKey());

        String plaintext = "Hello, JEP-470 corrected example!";
        byte[] ciphertext = encryptRSAOAEPSHA256(
                plaintext.getBytes(StandardCharsets.UTF_8),
                publicKey
        );
        String b64 = Base64.getEncoder().encodeToString(ciphertext);
        System.out.println("Ciphertext (Base64): " + b64);

    }

    private static PublicKey loadPublicKeyFromPem(String pem) throws Exception {
        // decode PEM text to a DEREncodable instance
        DEREncodable decoded = PEMDecoder.of().decode(pem);
        if (!(decoded instanceof PublicKey)) {
            throw new IllegalArgumentException("PEM did not decode to a PublicKey, got: " + decoded.getClass());
        }
        PublicKey pub = (PublicKey) decoded;
        return pub;
    }

    private static byte[] encryptRSAOAEPSHA256(byte[] data, PublicKey key) throws Exception {
        Cipher c = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        c.init(Cipher.ENCRYPT_MODE, key);
        return c.doFinal(data);
    }

    private static String getPublicKey() {
        return "-----BEGIN PUBLIC KEY-----\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnMLttZ9oYz+RhFbG1Gk3\n" +
                "hQGzhM06VNoy/7XH/kk4Qx0ihmVk0MR0mHhGZcJDnKmFNs+dnD66O7K2OPhlm/xG\n" +
                "5tsVMCgDnxZVtJhWytS6T9oR0c7sX7L6tXSaG6oSKY+UFWrZ/8XvN4XhT4Vddc1k\n" +
                "vUHr5WJgOkQHuDrMZmZr4h8fMwBhH+oYX9YAmEZz3r+M4R64GHwKCh4xRRqGx1fV\n" +
                "nWvXGcLuJDm0A1ukr35DJl1umzVN8m3r2mZqJ8s4t3JpDqE9hzld/h7dLOr5U0o5\n" +
                "hzMZd0mPKtsRoc0M0h7HqT5zV2UEoLfd9nYaTy9PK+zEJ8rNx2oUVZSwZONsFL93\n" +
                "UwIDAQAB\n" +
                "-----END PUBLIC KEY-----\n";
    }
}
Output: Ciphertext (Base64): XDvH0t3F0i4MI2Ecz3zlZ9zWwXonJ+Ffn6d8U8xBG4hcP20zMVqXIMzVzj5t6SqRCRnHhkEmlYHPWjrLYvWFcPBeVENLSSTFic/Sf2VOe5qgWCyeWnvmXvEHqdX2u6gsFRC42863mEfefkugaJrLnouaUTnVrq2vfva8164MOWzKucHB3eL1Fd16uhZiz01jdkG81ycBEZYtII9F7TVrcY3GvawXiy3c9hPGO4uG1C8kxbBL6+Iyh+l6ewjcHA8oujc7gzZkkb4k5DKSs733n0/9ktmXBvFbTvQqO6kvUaETbeqiNzQCfExRKJv3aVOlSygDxNhRmGm3v9sFCORlkg==

7. Conclusion

JEP 470 makes working with keys and certificates in Java much easier. With Java 25, the JDK can now read and write PEM files directly. This removes the long-standing need to depend on libraries like BouncyCastle just for basic PEM parsing.

The new PEMDecoder and PEMEncoder APIs reduce boilerplate code and keep sensitive key data inside the JDK’s trusted security layer. This makes applications simpler, safer, and easier to maintain.

For existing projects, migration is simple. You don’t need to change your encryption or signing logic. Just replace the old PEM parsing code with the new Java 25 APIs and test with your existing keys and certificates.

In short, Java 25 finally makes PEM a first-class citizen in Java — less code, fewer dependencies, and safer key handling.

Leave a Comment