JEP 527: Post-Quantum Hybrid Key Exchange for TLS 1.3 in Java 27
-
Last Updated: September 23, 2026
-
By: javahandson
-
Series

Java 27 brings post-quantum hybrid key exchange to TLS 1.3 through JEP 527. The name sounds heavy, but the idea is quite simple. Java now mixes a trusted classic algorithm with a new quantum-resistant one when two systems agree on a secret key.
Every Java backend talks to something over HTTPS. It may call a payment gateway, a partner bank, a cloud API or another microservice. All of this traffic depends on TLS. And TLS depends on key exchange algorithms that today’s computers cannot break.
The worry is about tomorrow. A large enough quantum computer could break some of these algorithms in the future. So JEP 527 adds a second layer of protection right now, while the risk is still far away.
The good part? Most of us do not need to change a single line of code. Java 27 turns on the hybrid scheme X25519MLKEM768 by default. It also keeps it at the top of the preference list.
In this article, we will go step by step. First we will look at the problem. Then we will see how ML-KEM works, what “hybrid” really means, how to configure it and how to verify it. We will also cover the traps that can quietly switch the feature off.
Before going deep, here is a quick summary of the JEP. Keep this table handy when you explain the feature to your team.
| Item | Details |
|---|---|
| JEP | 527: Post-Quantum Hybrid Key Exchange for TLS 1.3 |
| Released in | JDK 27 (GA on 15 September 2026) |
| Component | security-libs / javax.net.ssl |
| Provider | SunJSSE (the JDK’s built-in TLS implementation) |
| New named groups | X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024 |
| Default | X25519MLKEM768 enabled and most preferred |
| Code change needed | No, for apps that use the default named groups |
| TLS versions | TLS 1.3 only |
Let’s say your Spring Boot service calls a payment API over HTTPS. Before any JSON moves, the client and the server do a TLS handshake. Think of it as a short meeting where both sides agree on the rules.
Client Server | | | ---- ClientHello (groups, key shares) ->| | | | <--- ServerHello (chosen key share) ----| | | | Both sides now compute the SAME | | shared secret without sending it | | | | <====== Encrypted application data ===>|
Inside this meeting, the most important step is key exchange. Both sides want the same secret key. However, they must never send that key over the network in plain form.
TLS 1.3 solves this with Diffie-Hellman style math. The client sends a public value. The server sends its own public value. Each side then mixes its private value with the other side’s public value. Both land on the same secret, and a listener in the middle cannot work it out.
In TLS terms, these key exchange schemes are called “named groups”. Common ones are x25519, secp256r1 and secp384r1. Most modern TLS 1.3 traffic today uses x25519.
| 💡 Interview Insight In TLS 1.3, the client usually sends its key share inside the very first message (ClientHello). That is why TLS 1.3 needs only one round trip for the handshake. Interviewers love to ask how TLS 1.3 became faster than TLS 1.2, and this is the main answer. |
A normal computer works with bits. Each bit is either 0 or 1. A quantum computer works with qubits, and it can run some special algorithms that normal computers cannot run well.
RSA depends on how hard it is to factor very large numbers. Elliptic-curve Diffie-Hellman (ECDH) depends on another hard problem called the discrete logarithm. For a classical computer, both problems would take an impossible amount of time.
In 1994, Peter Shor published a quantum algorithm that can solve both problems fast. On a big and stable quantum computer, Shor’s algorithm could break RSA and ECDH. That includes x25519, which protects most TLS traffic today.
Let me be clear here. Such a machine does not exist today. Nobody can break your HTTPS traffic with a quantum computer right now. So why is Java acting today?
The answer is an attack style called “harvest now, decrypt later”. An attacker records your encrypted traffic today. They cannot read it yet, so they just store it. Storage is cheap, and they can wait for years.
Today (2026)
Client ===== encrypted TLS traffic =====> Server
|
Attacker copies and stores it
Some year in the future
Stored traffic --> Large quantum computer --> Breaks old key exchange --> Reads dataNow think about data that must stay secret for 10 or 20 years. Bank records, card data, health records, government files and trade secrets all fall in this group. If someone captures that traffic in 2026 and breaks it in 2036, the damage is still real.
This is exactly why the JEP says the move to quantum-resistant algorithms is urgent. The threat comes later, but the recording happens now.
| 💡 Interview Insight If an interviewer asks “Why adopt post-quantum crypto when quantum computers are not ready?”, answer with harvest now, decrypt later. Key exchange protects confidentiality, so it has to move first. Signatures can wait a little, because a future attacker cannot go back in time and fake a handshake that already happened. |
Post-quantum cryptography (PQC) means algorithms that stay safe even against large quantum computers. They run on normal hardware. They just use math problems that Shor’s algorithm cannot solve.
ML-KEM stands for Module-Lattice-Based Key Encapsulation Mechanism. NIST standardised it in August 2024 as FIPS 203. You may also know its earlier name, CRYSTALS-Kyber. Its security comes from hard problems on lattices, which are grids of points in many dimensions.
ML-KEM does not work exactly like Diffie-Hellman. It is a KEM, and the flow has three simple steps:
In TLS, the client plays the receiver and the server plays the sender. The client sends an ML-KEM public key in ClientHello. Then the server sends back the ciphertext in ServerHello. At the end, both hold the same shared secret.
ML-KEM comes in three sizes: ML-KEM-512, ML-KEM-768 and ML-KEM-1024. A bigger number gives more security and bigger keys. JEP 527 uses ML-KEM-768 and ML-KEM-1024.
JEP 527 did not appear from nowhere. Java added the pieces over a few releases:
| JDK | JEP | What it added |
|---|---|---|
| 21 | JEP 452 | The KEM API (javax.crypto.KEM) |
| 24 | JEP 496 | ML-KEM algorithm in the JDK |
| 24 | JEP 497 | ML-DSA, a quantum-resistant signature algorithm |
| 27 | JEP 527 | Hybrid ML-KEM key exchange inside TLS 1.3 |
Since JDK 24, you can already use ML-KEM directly with the KEM API. Here is a small example. You will not need this for TLS, but it shows what happens under the hood.
import javax.crypto.KEM;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.NamedParameterSpec;
public class MlKemDemo {
public static void main(String[] args) throws Exception {
// Receiver (TLS client) creates an ML-KEM-768 key pair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM");
kpg.initialize(NamedParameterSpec.ML_KEM_768);
KeyPair receiverKeys = kpg.generateKeyPair();
KEM kem = KEM.getInstance("ML-KEM");
// Sender (TLS server) encapsulates a fresh secret
KEM.Encapsulator enc = kem.newEncapsulator(receiverKeys.getPublic());
KEM.Encapsulated result = enc.encapsulate();
SecretKey senderSecret = result.key();
byte[] ciphertext = result.encapsulation();
// Receiver decapsulates and gets the same secret
KEM.Decapsulator dec = kem.newDecapsulator(receiverKeys.getPrivate());
SecretKey receiverSecret = dec.decapsulate(ciphertext);
System.out.println("Ciphertext size: " + ciphertext.length + " bytes");
System.out.println("Secrets match: " + java.util.Arrays.equals(
senderSecret.getEncoded(), receiverSecret.getEncoded()));
}
}Run it on JDK 24 or later. You should see a ciphertext of 1088 bytes and “Secrets match: true”. JEP 527 simply does this same dance inside the TLS handshake for you.
This is the most important idea in JEP 527. Java does not throw away x25519 and replace it with ML-KEM. Instead, it runs both at the same time and joins the results.
Traditional ECDHE (X25519) --> secret A
Post-quantum KEM (ML-KEM-768) --> secret B
Hybrid secret = B + A (joined together)
|
v
TLS 1.3 key schedule --> session keysGood question. ML-KEM is new. Experts have studied x25519 for many years, and it has held up well. ML-KEM has not faced the same amount of real-world attack and analysis yet.
So the hybrid approach plays it safe from both sides:
In simple words, the connection stays secure as long as at least one of the two algorithms holds. That is a solid way to move to new crypto without betting everything on it.
For X25519MLKEM768, the client sends one combined key share. This share holds an ML-KEM-768 public key and an X25519 public key, placed one after the other. The server replies with an ML-KEM ciphertext plus its own X25519 public key.
Each side then computes two secrets and joins them into one 64-byte value. After that, the normal TLS 1.3 key schedule takes over. Nothing else in TLS changes. Certificates, cipher suites and record encryption all stay the same.
| 💡 Interview Insight A crisp one-liner for interviews: “Hybrid key exchange is secure if either component is secure.” Then add why the industry chose hybrid first. The new PQC algorithms are young, so we keep the proven classical one as a safety net. |
JEP 527 adds three new named groups to the SunJSSE provider. Their names match the official IANA names, so they work with other TLS stacks too.
| Named group | Classical part | Post-quantum part | Enabled by default |
|---|---|---|---|
| X25519MLKEM768 | X25519 (ECDHE) | ML-KEM-768 | Yes (first choice) |
| SecP256r1MLKEM768 | secp256r1 / P-256 (ECDHE) | ML-KEM-768 | No |
| SecP384r1MLKEM1024 | secp384r1 / P-384 (ECDHE) | ML-KEM-1024 | No |
X25519MLKEM768 has become the common choice across the industry. Chrome, Firefox, Cloudflare and OpenSSL 3.5 all support it. So a Java 27 client has the best chance of a hybrid match with this group.
The other two groups exist mainly for compliance. Some regulated setups must use NIST curves like P-256 or P-384. For example, SecP384r1MLKEM1024 fits teams that want the highest security level on both sides.
Now let’s walk through a real handshake. We will take two cases, one with a modern server and one with an older server.
By default, a Java 27 client sends two key shares in ClientHello. One is the hybrid X25519MLKEM768 share, and the other is a plain x25519 share. It also lists all the groups it supports, in order of preference.
Java 27 Client supported_groups: X25519MLKEM768, x25519, secp256r1, ... key_share: X25519MLKEM768, x25519 Modern Server (supports hybrid) picks: X25519MLKEM768 Result: hybrid post-quantum key exchange, one round trip
An older server does not know X25519MLKEM768. It simply ignores that share and picks the plain x25519 share instead. The handshake still finishes in one round trip, just without post-quantum protection.
That is why the client sends both shares. If it sent only the hybrid share, older servers would reply with a HelloRetryRequest. That costs one extra round trip on every new connection.
Here is the full default order in JDK 27, straight from the JEP:
X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192
Notice that only one hybrid group sits in the default list. If you need SecP256r1MLKEM768 or SecP384r1MLKEM1024, you must enable it yourself. We will see how in a moment.
| 💡 Interview Insight In TLS 1.3, the client suggests and the server decides. The client lists groups in order of preference, but the server makes the final choice. So a Java 27 client alone cannot force a hybrid connection. Both sides need support for the same hybrid group. |
For most applications, the answer is no. However, there are a few cases where you will not get the feature automatically. Let’s look at both sides.
You get hybrid key exchange by default when all of these are true:
These are the traps I would check first in any real project:
| 💡 Interview Insight A common production surprise: the team upgrades to JDK 27 but hardened JVM flags from years ago still pin jdk.tls.namedGroups. The app works fine, but it never uses hybrid key exchange. Always grep your startup scripts, Dockerfiles and Helm charts for this property. |
Most teams should stay with the defaults. Still, you may need control for compliance, testing or a partner that needs a specific group. Java gives you two ways.
This property sets the groups for the whole JVM. The order you give is the order of preference.
# Prefer hybrid groups, keep classic ones as fallback
java -Djdk.tls.namedGroups=X25519MLKEM768,SecP256r1MLKEM768,x25519,secp256r1 \
-jar payment-service.jarBe careful here. Whatever you list replaces the default list fully. If you forget a classical group, older servers may fail the handshake with you.
For finer control, set the groups on one connection with SSLParameters.setNamedGroups(). This method has existed since JDK 20, and now it also accepts the hybrid names.
import javax.net.ssl.*;
SSLSocket socket = (SSLSocket) SSLContext.getDefault()
.getSocketFactory()
.createSocket("api.partnerbank.com", 443);
SSLParameters params = socket.getSSLParameters();
params.setProtocols(new String[] { "TLSv1.3" });
params.setNamedGroups(new String[] {
"SecP256r1MLKEM768", // hybrid, NIST curve
"X25519MLKEM768", // hybrid, most common
"secp256r1", // classic fallback
"x25519" // classic fallback
});
socket.setSSLParameters(params);
socket.startHandshake();Most modern code uses HttpClient rather than raw sockets. You can pass the same SSLParameters to its builder:
import java.net.URI;
import java.net.http.*;
import javax.net.ssl.SSLParameters;
SSLParameters params = new SSLParameters();
params.setProtocols(new String[] { "TLSv1.3" });
params.setNamedGroups(new String[] {
"X25519MLKEM768", "x25519", "secp256r1"
});
HttpClient client = HttpClient.newBuilder()
.sslParameters(params)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://pq.cloudflareresearch.com"))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());Honestly, you do not need this code just to get the default behaviour. Use it only when you really want to change the order or add the other two hybrid groups.
Enabling a feature is one thing. Proving that it works in your environment is another. Here are two simple ways to check.
The JDK can print handshake details. Run your app with the debug flag and filter for the ML-KEM group names:
java -Djavax.net.debug=ssl:handshake -jar payment-service.jar 2>&1 \
| grep -i "mlkem"Look at the ClientHello and ServerHello sections. In ClientHello, X25519MLKEM768 should appear under “supported_groups” and “key_share”. In ServerHello, the chosen key share tells you what the server picked. If it shows x25519, the server does not support hybrid yet.
Debug logs are very noisy. Use them in a test environment, not in production.
OpenSSL 3.5 and later support ML-KEM hybrid groups. You can start a test server that accepts only the hybrid group:
openssl s_server -accept 8443 -tls1_3 \
-cert server.pem -key server-key.pem \
-groups X25519MLKEM768 -wwwNow point a Java 27 client at https://localhost:8443. If the handshake passes, you know hybrid key exchange works end to end. Then try the same thing on JDK 25. The handshake fails, because the old JDK has no hybrid group to offer.
There is no new Spring property or annotation for JEP 527. It lives fully inside the JDK. If your Spring Boot app calls APIs through a client that uses JSSE, it benefits the moment it runs on JDK 27.
Your @Service code
|
RestClient / WebClient / RestTemplate
|
HTTP client library (JDK HttpClient, Apache HttpClient, ...)
|
javax.net.ssl (SunJSSE)
|
TLS 1.3 with X25519MLKEM768
|
TCPThe same idea applies on the server side. An embedded Tomcat, Jetty or Undertow using JSSE on JDK 27 will accept hybrid key exchange from modern clients. Still, check two things in real projects:
In many companies, TLS from the internet ends at a cloud load balancer. So JEP 527 matters most for internal service-to-service calls and outgoing calls to partners.
Hybrid key exchange is not free. The math is fast, but the keys are much bigger. Let’s look at the numbers.
| Named group | Client key share | Server key share |
|---|---|---|
| x25519 | 32 bytes | 32 bytes |
| X25519MLKEM768 | 1,216 bytes | 1,120 bytes |
| SecP256r1MLKEM768 | 1,249 bytes | 1,153 bytes |
| SecP384r1MLKEM1024 | 1,665 bytes | 1,665 bytes |
Because a Java 27 client sends both a hybrid share and an x25519 share, the ClientHello grows by more than 1 KB. It can now go beyond a single network packet of about 1,500 bytes. On the CPU side, ML-KEM is fast, often faster than the elliptic-curve part. The real cost is size, not speed.
Some old firewalls, proxies and TLS inspection boxes expect a small ClientHello that fits in one packet. When the message splits across two packets, a few of them misbehave. They may drop the connection or time out.
Browsers faced this same issue when they rolled out hybrid key exchange in 2024. Most vendors have fixed it since then. Even so, enterprise networks often run old devices for years.
| 💡 Interview Insight If a partner connection breaks after the JDK 27 upgrade, do not disable TLS 1.3 in panic. First remove X25519MLKEM768 from jdk.tls.namedGroups for that one service. If the handshake works again, you have found a middlebox size problem. Raise it with the network team. |
It is easy to oversell this feature. So let’s be precise about its limits.
A fair summary sounds like this: Java 27 adds post-quantum protection to TLS 1.3 key exchange. It is one important step in a long migration, not the whole journey.
Here is a quick side-by-side view to help you remember the difference:
| Aspect | Before (JDK 26 and older) | Java 27 with JEP 527 |
|---|---|---|
| Default first group | x25519 | X25519MLKEM768 |
| Safe against classical attacks | Yes | Yes |
| Safe against future quantum attacks | No | Yes, when both sides support hybrid |
| Harvest now, decrypt later | Exposed | Protected |
| ClientHello size | Small | About 1.2 KB larger |
| Code changes | Not applicable | None for default setups |
JEP 527 is a small change from the outside but a big step for the Java platform. Quantum computers that can break x25519 do not exist yet. Still, attackers can record traffic today and wait. That makes key exchange the first place where post-quantum protection matters.
Java handles this in a careful way. It does not throw away proven elliptic-curve crypto. Instead, it pairs x25519 with ML-KEM-768 and joins both secrets. As long as one of them holds, your TLS session stays safe.
For most teams, the work is simple. Move to JDK 27 and check that no old flag overrides the named groups. Then test the full network path, including proxies and load balancers. After that, the JDK negotiates hybrid key exchange on its own whenever the other side supports it.
For a Java developer, this is the best kind of security upgrade. The heavy crypto lives deep inside the JDK. Your business code stays the same, and your data stays protected for many years to come.