Post-quantum cryptography shipped in the JDK — your TLS migration starts now
ML-KEM and ML-DSA are now in standard Java. Harvest-now-decrypt-later makes this a today problem, not a 2035 one. A practical migration playbook for TLS, JWT, and key management — from the architecture, developer, and DevOps angles.
On this page
# The Problem
An attacker doesn’t need a quantum computer today to hurt you tomorrow. They need a packet capture. Right now, someone can record your TLS-encrypted traffic — the payments, the health records, the session tokens — and simply store it. The encryption holds today. But the day a cryptographically-relevant quantum computer arrives, that stored ciphertext gets decrypted retroactively. Everything you encrypted with classical RSA or elliptic-curve crypto and that still needs to be secret then is already exposed, right now, in a warehouse of recorded traffic.
This is harvest-now-decrypt-later, and it’s the reason post-quantum cryptography stopped being a research topic and became a compliance line item in 2026. If your data must stay confidential for ten or twenty years — and in finance, healthcare, and government it must — the clock started when NIST finalized the standards, not when the quantum computer ships.
Here’s the part that makes this actionable rather than terrifying: the JDK you’re already upgrading to for the LTS ships the answer. ML-KEM and ML-DSA are in standard Java now. You don’t need a third-party crypto provider to start. You need a plan.
# What’s Actually in the JDK
NIST finalized its post-quantum standards on 13 August 2024, and the JDK moved fast:
- JEP 496 (ML-KEM) — quantum-resistant key encapsulation (FIPS 203). Landed as a preview in JDK 24, finalized in JDK 25. Parameter sets ML-KEM-512, -768, -1024. This is what protects TLS key exchange.
- JEP 497 (ML-DSA) — quantum-resistant digital signatures (FIPS 204). Parameter sets ML-DSA-44, -65, -87 (default 65). This is what protects JWT signing, code signing, and certificate verification.
Both are in the standard JCA provider — no BouncyCastle required for these primitives. (SLH-DSA, the hash-based signature scheme, remains BouncyCastle-only for now.) And the roadmap is public and dated: native hybrid TLS 1.3 is targeted for JDK 27, September 2026.
ML-KEM does key encapsulation — establishing a shared secret over an untrusted channel (the TLS handshake). ML-DSA does digital signatures — JWT signing, TLS certificate verification, code signing. You’ll usually need both, in different places.
# The Architecture Perspective
You cannot rip out RSA and ECC overnight — certificates, HSMs, protocol interop, and partner systems all assume classical crypto. So the realistic 2026 posture isn’t “replace everything,” it’s crypto-agility and hybrid: prepare to run classical and post-quantum side by side, and make the algorithm a configurable choice rather than a hardcoded assumption.
flowchart TD
A["Inventory crypto:<br/>build a CBOM"] --> B{Data lifetime > 10 years?}
B -->|yes| C["High priority:<br/>harvest-now-decrypt-later exposed today"]
B -->|no| D["Lower priority"]
C --> E["Adopt hybrid classical + PQC<br/>where you control both ends"]
E --> F["mTLS internal → signing → external TLS on JDK 27"]
classDef start fill:#2563eb,color:#fff,stroke:#1e40af;
classDef hot fill:#dc2626,color:#fff,stroke:#991b1b;
classDef cool fill:#475569,color:#fff,stroke:#334155;
classDef plan fill:#059669,color:#fff,stroke:#065f46;
class A start;
class C hot;
class D cool;
class E,F plan;Two architectural moves anchor the whole program:
A cryptographic bill of materials (CBOM). You can’t migrate what you can’t see. Inventory every place classical crypto lives — TLS endpoints, JWT signing keys, stored encrypted data, HSM-backed keys, code-signing — and sort it by data lifetime. Anything that must stay secret for 10+ years is harvest-now-decrypt-later exposed today, and jumps to the front of the queue.
Hybrid, not replacement. Combine a classical and a post-quantum algorithm so a break in either scheme doesn’t sink you — belt and suspenders. This is exactly what JDK 27’s native hybrid TLS 1.3 is heading toward; until it lands, you apply hybrid at the application layer where you control both ends of the connection.
The lasting architectural win isn’t adopting one PQC algorithm — it’s making algorithm selection a configuration so the next rotation is a deployment, not a rewrite. NIST standards will evolve; agility is the property that lets you keep up without a re-architecture each time.
# The Developer Perspective
Because these are in the JCA, the developer experience is familiar. Key encapsulation with ML-KEM:
// ML-KEM: establish a shared secret (the TLS-handshake job), JDK 25.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM");
KeyPair kp = kpg.generateKeyPair();
KEM kem = KEM.getInstance("ML-KEM");
KEM.Encapsulated enc = kem.newEncapsulator(kp.getPublic()).encapsulate();
SecretKey shared = enc.key(); // sender's shared secret
// Receiver decapsulates enc.encapsulation() with the private key to get the same key.Signing and verifying with ML-DSA — e.g. for a JWT or an artifact:
// ML-DSA: sign and verify (JWT / code signing), JDK 25. Default set ML-DSA-65.
KeyPair kp = KeyPairGenerator.getInstance("ML-DSA").generateKeyPair();
Signature signer = Signature.getInstance("ML-DSA");
signer.initSign(kp.getPrivate());
signer.update(payloadBytes);
byte[] sig = signer.sign();
Signature verifier = Signature.getInstance("ML-DSA");
verifier.initVerify(kp.getPublic());
verifier.update(payloadBytes);
boolean ok = verifier.verify(sig);The pattern that keeps you agile — never hardcode the algorithm:
// Algorithm from config, not a string literal. Rotating is a deploy, not a diff.
String kem = config.get("crypto.kem", "ML-KEM"); // swap without code changes
KeyPairGenerator.getInstance(kem);ML-KEM and ML-DSA are finalized in the JDK 25 JCA. If you’re planning the LTS bump anyway, the primitives arrive with it — no new dependency, no third-party provider for the core algorithms. That removes the usual “wait for the ecosystem” excuse.
Hybrid TLS 1.3 is targeted for JDK 27 (Sep 2026), not available today. Don’t promise external-facing PQC TLS before it lands. Where you need protection now, apply hybrid at the application layer on connections where you control both ends (internal mTLS, signing).
# The DevOps Perspective
PQC changes the shape of your traffic and artifacts, and that has operational consequences you must measure before rollout.
Bigger keys and signatures — budget for it. ML-KEM ciphertexts and ML-DSA signatures are substantially larger than their ECC/Ed25519 equivalents. That means bigger TLS handshakes, larger JWTs, and more storage for signed artifacts. Larger handshakes can push past an MTU and add round-trips; larger tokens inflate every request header. Load-test the real sizes before you flip anything.
flowchart LR
A["PQC keys/signatures<br/>larger than classical"] --> B["Bigger TLS handshakes<br/>(MTU / extra round-trips)"]
A --> C["Larger JWTs<br/>(header bloat per request)"]
A --> D["More storage<br/>for signed artifacts"]
B --> E["Measure under load<br/>before rollout"]
C --> E
D --> E
classDef src fill:#d97706,color:#fff,stroke:#92400e;
classDef eff fill:#2563eb,color:#fff,stroke:#1e40af;
classDef gate fill:#059669,color:#fff,stroke:#065f46;
class A src;
class B,C,D eff;
class E gate;Phase the rollout by control. Start where you own both ends — internal service-to-service mTLS — because you can upgrade client and server together and measure the impact in a blast-radius you control. Then signing (JWT/artifacts). Save external-facing TLS for last, once hybrid 1.3 is available and partners can interop.
The compliance story gets simpler with JDK-native support. Auditors increasingly ask where classical crypto lives and what your migration plan is. A CBOM plus PQC that ships in the standard JDK (rather than a bolted-on provider) makes the attestation cleaner — you’re using platform-standard, FIPS-aligned algorithms, not a third-party integration you have to separately vouch for.
Pros
- ML-KEM and ML-DSA ship in the standard JDK 25 JCA — no extra provider
- Familiar KeyPairGenerator/Signature/KEM APIs
- Hybrid classical+PQC hedges against a break in either scheme
- JDK-native support simplifies compliance attestation
- Crypto-agility makes future rotations a deploy, not a rewrite
Cons
- Larger keys/signatures inflate handshakes, JWTs, and storage
- Native hybrid TLS 1.3 not until JDK 27 (Sep 2026)
- SLH-DSA still needs BouncyCastle
- Requires a full crypto inventory (CBOM) to do right
- External-facing rollout gated on partner interop
# Putting It Together
Post-quantum cryptography in Java is no longer “wait for the ecosystem” — the algorithms ship in the LTS you’re already adopting, and harvest-now-decrypt-later means the risk to long-lived data is a today problem. The realistic path is a CBOM to see where classical crypto lives, hybrid classical+PQC where you control both ends, and crypto-agility so the next standards shift is a config change. Start with internal mTLS, move to signing, and be ready for external TLS when JDK 27’s hybrid 1.3 lands. The migration is a program, not a patch — and the JDK just handed you the tools to begin it.
# References
- JEP 496: Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism — OpenJDK
- JEP 497: Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm — OpenJDK
- The Post-Quantum Security Timeline for Java: JEP 496, JEP 527 — Java Code Geeks
- Post-Quantum Cryptography in Java: What the NIST Standards Mean for Your TLS, JWT, and Key Management Code — Java Code Geeks
- The Java Platform and Post-Quantum Cryptography: From Threat Models to JDK Delivery — Neat Guy Coding
- Implementing Quantum-Resistant ML-KEM and ML-DSA in Java — Java Code Geeks
Click to show appreciation
Discussion