How to Use Post-Quantum TLS in Java 27 (JEP 527)
Writing
SECURITY
Published August 28, 20268 min read

How to Use Post-Quantum TLS in Java 27 (JEP 527)

Learn how to use post-quantum TLS in Java 27 with JEP 527. Enable the X25519MLKEM768 hybrid key exchange, verify the handshake, and tune jdk.tls.namedGroups.

Rabinarayan Patra - Software Development Engineer

By Rabinarayan Patra

SDE II at Amazon

post-quantum-tls-javajep-527x25519mlkem768ml-kemjdk-27java-security

Here's the uncomfortable part about quantum computers and TLS: you don't have to wait for them to be attacked by them. An attacker can record your encrypted traffic today, sit on it for years, and decrypt it the day a large enough quantum computer exists. It's called harvest-now-decrypt-later, and it's the reason post-quantum cryptography is a today problem, not a 2035 problem.

Java 27 does something about it without asking you to lift a finger. JEP 527 adds a hybrid post-quantum key exchange to TLS 1.3 and turns it on by default. Every TLS connection your HttpClient, RestClient, or JDBC driver makes now offers a quantum-safe key share alongside the classical one.

I pulled the exact details from the OpenJDK inside.java write-ups and the JDK 27 early-access builds. This post covers what actually changes in the handshake, how to confirm your client is really using the hybrid group, how to control the named groups with jdk.tls.namedGroups, how to enable it on the server side, and what I'd test before trusting it in production. If you've worked with the new PEM API in JDK 27, this is the other big security feature landing in the same release.

What is post-quantum TLS, and why does Java 27 add it now?

Post-quantum TLS is a key exchange that stays secure even against an attacker with a quantum computer, and Java 27 adds it because the threat is about recorded traffic, not future traffic. The classical key exchanges TLS has used for years, like X25519 and the NIST curves, fall to Shor's algorithm on a sufficiently large quantum machine.

The fix isn't to throw out the classical algorithms. It's to combine them. A hybrid key exchange runs a classical algorithm and a post-quantum algorithm together and mixes both shared secrets into the session keys. To break the session, an attacker has to break both. So if ML-KEM turns out to have a flaw, you still have X25519 protecting you, and if a quantum computer breaks X25519, you still have ML-KEM. You lose nothing and gain a hedge.

ML-KEM is the Module-Lattice Key Encapsulation Mechanism, standardized by NIST as FIPS 203. Java already shipped the raw ML-KEM primitive in JDK 24 through JEP 496. JEP 527 is the part that actually wires it into TLS 1.3, so you get the protection on real connections instead of having to build your own protocol.

What does JEP 527 actually change in the TLS handshake?

JEP 527 adds three hybrid named groups to the SunJSSE provider and enables one of them by default. The named groups are:

Named groupCombination
X25519MLKEM768X25519 ECDHE + ML-KEM-768
SecP256r1MLKEM768secp256r1 ECDHE + ML-KEM-768
SecP384r1MLKEM1024secp384r1 ECDHE + ML-KEM-1024

By default, a Java 27 TLS client offers two key shares in its ClientHello: one for X25519MLKEM768 and one for classical x25519. That dual key share is a thoughtful detail. If the server speaks the hybrid group, it picks X25519MLKEM768 and you get post-quantum protection. If the server is old and only knows x25519, it picks that one from the same ClientHello, so the handshake completes in the normal number of round trips with no HelloRetryRequest penalty.

The trade-off is size. An X25519MLKEM768 key share is roughly 1.2KB, because the ML-KEM-768 encapsulation key is 1184 bytes, versus 32 bytes for X25519 on its own. So your ClientHello grows by about a kilobyte. That matters for the rollout testing later, but it costs you nothing in code.

How do you confirm your Java 27 client is using post-quantum TLS?

Turn on JSSE handshake debugging and read the named group in the handshake output. Since the feature is on by default, there's nothing to enable first, you just need to prove it's happening.

java -Djavax.net.debug=ssl,handshake -jar your-app.jar

In the ClientHello you'll see the supported groups and the key_share extension listing X25519MLKEM768 ahead of x25519. In the ServerHello, the selected group tells you what was actually negotiated. If you see X25519MLKEM768 there, the session is post-quantum. If you see x25519, the peer didn't support the hybrid group and you fell back to classical.

A small standalone check is the fastest way to see it against a real endpoint:

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
 
public class TlsProbe {
    public static void main(String[] args) throws Exception {
        SSLSocketFactory factory =
            (SSLSocketFactory) SSLSocketFactory.getDefault();
        try (SSLSocket socket =
                 (SSLSocket) factory.createSocket("example.org", 443)) {
            socket.startHandshake();
            System.out.println("Protocol: " + socket.getSession().getProtocol());
            // Run with -Djavax.net.debug=ssl,handshake to see the
            // negotiated named group in the handshake log.
        }
    }
}

The standard JSSE session API doesn't expose the negotiated named group as a getter, so the handshake log is the source of truth. I keep -Djavax.net.debug=ssl,handshake handy for exactly this kind of check.

How do you control the named groups with jdk.tls.namedGroups?

Set the jdk.tls.namedGroups system property to an ordered, comma-separated list, and SunJSSE offers them in that order. This is how you prefer a different hybrid scheme, pin to post-quantum only for testing, or disable the hybrid groups if you hit an interop problem.

# Prefer the secp256r1 hybrid, then the X25519 hybrid, then classical fallbacks
java -Djdk.tls.namedGroups="SecP256r1MLKEM768,X25519MLKEM768,secp256r1,x25519" \
     -jar your-app.jar

You can do the same per connection with SSLParameters, which is cleaner when only one client needs special handling:

import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
 
SSLParameters params = tlsSock.getSSLParameters();
params.setNamedGroups(new String[] {
    "SecP256r1MLKEM768", "X25519MLKEM768", "secp256r1", "x25519"
});
tlsSock.setSSLParameters(params);

Two practical recipes. To force post-quantum only, so a test fails loudly if the peer can't do it, set the list to just X25519MLKEM768. To disable post-quantum entirely as a temporary fallback for a broken middlebox, set it to classical groups like x25519,secp256r1 and the hybrid shares disappear from your ClientHello.

How do you enable post-quantum key exchange on the server side?

Run your TLS server on JDK 27 and the hybrid groups are available automatically, because the server selects a group from what the client offers. There's no separate switch. The server side of the handshake just needs to know X25519MLKEM768, which SunJSSE does on JDK 27.

The catch is ordering. A server picks the first mutually supported group based on its own preference list, so if you've pinned jdk.tls.namedGroups on the server to a classical-only list from some older hardening guide, it will never choose the hybrid group even when the client offers it. Check your server's existing TLS configuration for a hardcoded named-group list and add the hybrid groups to the front. For a Spring Boot service behind its own TLS, that's the JVM flag on the server process, not anything in your application code.

This is also where you decide your security posture across services. If you run both client and server, putting X25519MLKEM768 first on both guarantees post-quantum protection on internal hops, which fits the least-privilege model I wrote about in my zero trust microservices guide.

What should you test before you roll it out?

Test interop and ClientHello size first, because those are where a hybrid key exchange bites you, not the cryptography itself. The math is solid. The failure mode is some box in the middle that doesn't like a bigger handshake.

Here's my checklist before flipping anything to production:

  • Interop with older peers. Point your Java 27 client at the actual endpoints it talks to and confirm the handshake still completes. With the default dual key share it should fall back to x25519 cleanly, but verify it instead of assuming.
  • Middleboxes and the larger ClientHello. The extra ~1.2KB can trip up old load balancers, deep-packet-inspection firewalls, or anything that makes assumptions about handshake size. Test through your real network path, not just localhost.
  • Handshake cost. Measure connection setup time under load. ML-KEM operations are fast, but the larger key share means more bytes on the wire per handshake.
  • A loud failure path. Run one test environment pinned to X25519MLKEM768 only, so if a dependency silently can't do post-quantum, your tests catch it instead of quietly downgrading in production.

The reason I'd turn this on deliberately rather than just inheriting the default is that "secure by default" only helps if it actually negotiates. A misconfigured server preference list or a grumpy middlebox can leave you on classical key exchange while you think you're protected. Run the handshake debug once per critical connection, confirm you see X25519MLKEM768, and then you know the harvest-now-decrypt-later window is closed for that traffic. That five-minute check is the whole point of the feature being real instead of theoretical.

For the full specification and testing guidance, see the OpenJDK write-ups on post-quantum hybrid key exchange in JDK 27 and the JDK 27 Quality Outreach heads-up, plus the NIST FIPS 203 ML-KEM standard that defines the post-quantum primitive.

Keep Reading

Frequently Asked Questions

What is post-quantum TLS in Java 27?

Post-quantum TLS in Java 27 is the hybrid key exchange added by JEP 527 to the TLS 1.3 implementation in SunJSSE. It combines a classical algorithm (X25519 ECDHE) with the quantum-resistant ML-KEM-768 into a single named group, X25519MLKEM768, that is enabled by default. The session stays secure as long as either the classical or the post-quantum part holds.

Which named groups does JEP 527 add?

JEP 527 adds three hybrid named groups to SunJSSE: X25519MLKEM768 (X25519 plus ML-KEM-768), SecP256r1MLKEM768 (secp256r1 plus ML-KEM-768), and SecP384r1MLKEM1024 (secp384r1 plus ML-KEM-1024). By default a Java 27 client offers two key shares, one for X25519MLKEM768 and one for classical x25519.

Do I have to change code to get post-quantum TLS in Java 27?

No. Any application using the javax.net.ssl APIs gets X25519MLKEM768 by default on JDK 27 with no code change. You only touch configuration if you want to reorder the named groups, prefer a different hybrid scheme, or disable the post-quantum groups for an interop problem, which you do with the jdk.tls.namedGroups system property or SSLParameters.setNamedGroups.

Why does post-quantum cryptography matter before quantum computers exist?

Because of harvest-now-decrypt-later. An attacker can record your encrypted traffic today and store it until a quantum computer can break the classical key exchange, then decrypt it retroactively. A hybrid key exchange like X25519MLKEM768 protects long-lived secrets recorded today, which is why enabling it now matters.

Rabinarayan Patra - Software Development Engineer

Rabinarayan Patra

SDE II at Amazon. Previously at ThoughtClan Technologies building systems that processed 700M+ daily transactions. I write about Java, Spring Boot, microservices, and the things I figure out along the way. More about me →

X (Twitter)LinkedIn

Stay in the loop

Get the latest articles on system design, frontend and backend development, and emerging tech trends, straight to your inbox. No spam.