Java gives you post-quantum building blocks before it gives you a post-quantum TLS handshake. For a server-side Java application behind a reverse proxy, the fastest route to hybrid ML-KEM (Module-Lattice-Based Key Encapsulation Mechanism, standardized as NIST FIPS 203) key exchange is to upgrade the proxy, not the JVM. When Java is the TLS client, hybrid key exchange depends on the JDK release and on the JSSE provider in use, so the only reliable answer comes from the release notes of the build you actually deploy, confirmed by a handshake test.
First decide which side of the handshake you own
A TLS key exchange is negotiated by the two stacks that perform the handshake, not by application code. Three cases cover almost every Java deployment, and they have different answers.
- Inbound traffic terminated by a proxy. An ingress controller, load balancer, CDN or nginx, Apache, HAProxy or Caddy instance does the handshake. The JVM never sees the key exchange. Upgrading the proxy is enough to offer hybrid groups to browsers and API clients.
- Inbound traffic terminated by the JVM. An embedded Tomcat, Jetty, Undertow or Netty listener with a Java keystore. Here the group list comes from the JDK TLS implementation (JSSE), or from the native TLS library behind the connector.
- Outbound traffic, Java as the client. HTTP clients calling partner APIs, JDBC and message broker drivers, object storage SDKs, mutual TLS between internal services. The Java side proposes the
supported_groupslist and the remote server picks one.
Order the work using harvest now, decrypt later (HNDL): an attacker who records traffic today and decrypts it years later is defeated by the key exchange, not by the certificate. That is why ML-KEM key establishment comes first and signature migration can follow. The IANA TLS registry lists the hybrid groups in use, including X25519MLKEM768 (codepoint 0x11EC), SecP256r1MLKEM768 and SecP384r1MLKEM1024 (IANA TLS parameters), and RFC 10024 specifies post-quantum traditional hybrid key agreement for TLS 1.3 (RFC 10024).
Where the JDK stands today
Two layers must be kept apart: the cryptographic primitives exposed through the Java Cryptography Architecture (JCA), and hybrid group support inside JSSE.
Primitives: available in current releases
JEP 452 added a Key Encapsulation Mechanism (KEM) API, javax.crypto.KEM, in JDK 21. JDK 24 added implementations of ML-KEM (JEP 496) and of ML-DSA (Module-Lattice-Based Digital Signature Algorithm, NIST FIPS 204) in JEP 497, reachable through the usual KeyPairGenerator, KEM and Signature APIs with algorithm names for each parameter set. Because JDK features carry forward, later releases include them. This is what you need for your own protocols, key wrapping, token formats and test harnesses. It is not, by itself, TLS.
Hybrid TLS groups: read the release notes of your JDK
Offering a hybrid group in a TLS 1.3 handshake is separate work inside JSSE: the group has to be registered, key shares generated and parsed, and the shared secret derived from both components. Whether the JDK you run does this, under which group names, and whether it is enabled by default, varies by release and by vendor build, since distributions backport selectively. Do not infer TLS support from the presence of ML-KEM in the JCA, and do not rely on an article written about a different build. Check the release notes of your exact JDK version, then verify empirically with the handshake test below.
| Layer | What to verify | Where to look |
|---|---|---|
| JCA primitives | ML-KEM and ML-DSA key pair generation, encapsulation, signature verification | JDK release notes, standard algorithm names documentation |
| JSSE hybrid groups | Whether the hybrid group appears in ClientHello or ServerHello | JDK release notes, plus a real handshake with debug logging |
| Native TLS behind a connector | Version of the native library used by the connector (ML-KEM groups need OpenSSL 3.5 or later in OpenSSL builds) | Dependency tree, native library version at runtime |
| Proxy or ingress | Group list and ordering, classical fallback still present | Proxy configuration and its own release notes |
Your options, in order of effort
Terminate inbound TLS at a proxy
This is the pragmatic default for server-side Java. The proxy needs a TLS library that implements the ML-KEM groups: in OpenSSL builds that means OpenSSL 3.5 or later. Configure the hybrid group first and keep classical groups after it, so clients without hybrid support still connect, and validate in staging before production. The step by step directives for the common servers are in our guide to enabling ML-KEM on nginx, Apache, HAProxy and Caddy. One practical caution: a hybrid key share is substantially larger than an X25519 key share, which can push the ClientHello across packet boundaries. Old middleboxes and badly behaved network appliances sometimes mishandle that, so test from the networks your clients actually use.
Upgrade the JDK where the JVM terminates TLS
If a Java listener faces clients directly, or if Java is the client in a connection you care about, the clean fix is to run a JDK release whose JSSE offers the hybrid group, then confirm it on the wire. JSSE exposes the documented system property jdk.tls.namedGroups to restrict and order the groups it offers. The names it accepts depend on the release, so take them from your release notes rather than copying an example, and always leave a classical group such as X25519 in the list. Do not pin a single post-quantum group in production.
Add a provider library, or route outbound traffic through a gateway
A third-party JCA or JSSE provider can supply algorithms and, in some cases, a TLS stack independent of the JDK. Weigh it carefully: installing a provider changes security behaviour for the whole JVM, it interacts with FIPS mode and with certificate path validation, and it becomes a dependency you must patch. Keep the change scoped to the SSLContext of the specific client when the library allows it, rather than reordering global providers. For outbound calls from legacy JVMs that cannot be upgraded, an egress proxy that performs the handshake is often simpler and easier to audit than modifying every application. Note too that connectors using a native TLS library (for example Netty with netty-tcnative) follow that library version, not the JDK.
Inventory Java crypto usage before changing anything
Every official timeline starts with discovery, and auditors ask for the inventory before they ask for the migration. These read-only commands give you a first pass; adapt the paths to your build layout.
# TLS and crypto entry points in application code
grep -rnE "SSLContext|SSLSocketFactory|HttpsURLConnection|TrustManager|KeyManagerFactory" --include="*.java" src/
# algorithm names and keystore formats hard coded in source or configuration
grep -rnE "RSA|ECDSA|SHA1|MD5|PKCS12|JKS" --include="*.java" --include="*.properties" --include="*.yml" .
# which TLS stack each service really uses
mvn -q dependency:tree | grep -Ei "netty|tcnative|conscrypt|bouncycastle|okhttp|httpclient"
./gradlew dependencies --configuration runtimeClasspath | grep -Ei "netty|tcnative|okhttp|httpclient"
# keys and certificates in a keystore, with their algorithms
keytool -list -v -keystore keystore.p12 -storetype PKCS12 | grep -E "Alias name|Signature algorithm name|Subject Public Key Algorithm"
Record more than TLS. Java applications also sign JSON Web Tokens and SAML assertions, verify signed JARs, encrypt database columns, and pin trust anchors. Note the quantum-vulnerable public key algorithms (RSA, ECDSA, EdDSA, ECDH, finite-field Diffie-Hellman and DSA are all affected by Shor's algorithm, whatever the key size), where each one is used, who owns it, and whether the algorithm is configurable without a code change. A machine-readable cryptography bill of materials is one practical way to keep that list current: see what a CBOM is and how to produce one. Keep an eye on the JDK security policy files as well, since jdk.tls.disabledAlgorithms and jdk.certpath.disabledAlgorithms in java.security already govern what your JVMs will accept.
Test what the handshake actually negotiates
Claims about support are worth nothing without a negotiated group. Run three checks, in this order.
# 1. external view of the listener (OpenSSL 3.5 or later on the client side)
openssl s_client -connect api.example.com:443 -groups X25519MLKEM768 </dev/null 2>&1 | grep -i "Negotiated TLS1.3 group"
# 2. the classical fallback must still work
openssl s_client -connect api.example.com:443 -groups X25519 </dev/null 2>&1 | grep -i "Negotiated TLS1.3 group"
# 3. what the Java client offers, and what the peer selects
java -Djavax.net.debug=ssl:handshake -jar client.jar 2>&1 | grep -iE "supported_groups|named group|key_share"
Step 3 is the decisive one for Java as a client: the debug output shows the groups in the ClientHello and the group chosen for the key exchange. If the hybrid group never appears, the JDK build does not offer it, whatever the documentation suggested. Keep the output as evidence, with the JDK version string and the date. Our free scanner covers step 1 from the outside, including TLS versions, cipher suites and the certificate chain; what a result does and does not mean is described in our methodology page.
How this lands in audits
The inventory you just built is the artefact most frameworks ask for, and the following is information rather than legal advice. PCI DSS v4.0.1 requirement 12.3.3, mandatory since 31 March 2025, asks for a documented inventory of cipher suites and protocols in use, reviewed at least every 12 months, with a strategy for anticipated changes in cryptographic vulnerabilities: see our page on PCI DSS requirement 12.3.3. Under DORA, Commission Delegated Regulation (EU) 2024/1774 Art. 6 requires an encryption and cryptographic controls policy with provisions for updating or changing cryptographic technology as cryptanalysis develops, and Art. 7 requires key management and a register of certificates for ICT assets supporting critical or important functions (EUR-Lex). Interpretation, not text: in a Java estate, the practical proof of Art. 6 is that your services can change groups and algorithms through configuration and a JDK or proxy upgrade, not through a rewrite. For algorithm status, NIST published FIPS 203, 204 and 205 as final standards on 13 August 2024 (FIPS 203), while NIST IR 8547, which proposes deprecating 112-bit security public key algorithms after 2030 and disallowing quantum-vulnerable public key algorithms after 2035, remains an initial public draft (NIST IR 8547 ipd).
The next concrete step
Pick one externally reachable Java service and scan your domain with PQC Status to see which group it negotiates today. If the answer is classical only and a proxy terminates TLS, upgrade that proxy to a build with OpenSSL 3.5 or later and put the hybrid group ahead of X25519 in staging. If the JVM terminates TLS, or if Java is the client in a connection that carries long-lived secrets, record the JDK version in your inventory, read its release notes for hybrid key exchange, and capture a handshake log as evidence either way. Re-scan after each change, and keep the classical fallback until your clients no longer need it.