Yes, we're now running our Black Friday Sale. All Access and Pro are 33% off until 2nd December, 2025:
Key Encapsulation Mechanism API in Java
Last updated: November 20, 2025
1. Introduction
JEP 452 introduces a standardized Key Encapsulation Mechanism (KEM) API into the Java Platform (in JDK 21) as part of the javax.crypto package.
This addition fills a long-standing gap in the Java Cryptography Architecture (JCA) — providing a standard, provider-neutral way to securely establish shared symmetric keys using public-key cryptography.
Before Java 21, developers relied on custom combinations of Cipher, KeyAgreement, or KeyGenerator objects to simulate this process. Those approaches were often error-prone and inconsistent across providers.
With KEM, the entire key-exchange step becomes a single, well-defined API.
In this tutorial, we’ll explore the KEM API in Java 21.
2. What Is a Key Encapsulation Mechanism?
A KEM is a public-key cryptographic primitive for securely establishing a shared symmetric key between two parties.
It consists of three functions:
- A key pair generation, which outputs a public and private key.
- A key encapsulation, where the sender uses the recipient’s public key to produce a shared secret key (K) and a key encapsulation message (ciphertext).
- A key decapsulation, where the receiver uses their private key and the encapsulation message to recover the same secret key (K).
Unlike using Cipher directly, a KEM is format-agnostic, avoids padding issues, and works seamlessly for both classical and post-quantum algorithms.
It’s also a fundamental building block for modern protocols such as Transport Layer Security (TLS), Hybrid Public Key Encryption (HPKE, RFC 9180), and emerging post-quantum cryptography (PQC) schemes.
3. Supported Algorithms
The JEP 452 implementation supports several standardized KEMs:
- RSA-KEM: A KEM built on top of RSA key pairs.
- Elliptic Curve Integrated Encryption Scheme (ECIES): Elliptic-curve-based scheme.
- DHKEM: Diffie-Hellman KEM defined in RFC 9180.
Providers can add new KEM implementations, such as post-quantum KEMs like Kyber, without changing our application logic.
4. Encapsulating a Shared Secret
On the sender’s side, we need to encapsulate a new shared secret using the receiver’s public key.
Let’s look at how to perform encapsulation with our KemUtils class:
public class KemUtils {
public record KemResult(SecretKey sharedSecret, byte[] encapsulation) {}
public static KemResult encapsulate(String algorithm, PublicKey publicKey) throws Exception {
KEM kem = KEM.getInstance(algorithm);
KEM.Encapsulator encapsulator = kem.newEncapsulator(publicKey);
KEM.Encapsulated result = encapsulator.encapsulate();
return new KemResult(result.key(), result.encapsulation());
}
}
The encapsulate() method carries out the entire KEM encapsulation process. It first obtains a KEM instance for the specified algorithm (such as “DHKEM”) and creates an Encapsulator using the recipient’s public key. This encapsulator performs the sender’s role in generating both the shared secret key and the associated encapsulation data through the encapsulate() call. The method then packages these two outputs into a KemResult record, which it returns to the caller.
To make KEM operations easier to use, we define a simple record named KemResult:
public record KemResult(SecretKey sharedSecret, byte[] encapsulation) {}
The KemResult record serves as a simple, immutable container for the two outputs of a KEM operation — a shared symmetric key (SecretKey) and the corresponding encapsulation blob (byte[]).
The sender then transmits the encapsulation byte array to the receiver, while keeping the sharedSecret private for encryption or key derivation.
5. Decapsulating the Shared Secret
On the receiver’s side, we need to recover the same shared secret using the encapsulation blob and the receiver’s private key.
The receiver calls the decapsulate() method from our utility class, passing the algorithm name (“DHKEM”), the receiver’s private key, and the encapsulation blob received from the sender:
public static KemResult decapsulate(String algorithm, PrivateKey privateKey, byte[] encapsulation)
throws Exception {
KEM kem = KEM.getInstance(algorithm);
KEM.Decapsulator decapsulator = kem.newDecapsulator(privateKey);
SecretKey recoveredSecret = decapsulator.decapsulate(encapsulation);
return new KemResult(recoveredSecret, encapsulation);
}
The method begins by creating a KEM instance for the specified algorithm and initializes a Decapsulator using the receiver’s private key. The method then calls decapsulate(encapsulation) to reconstruct the shared symmetric key (SecretKey) from the encapsulation blob received from the sender. Finally, it returns a new KemResult containing the recovered secret and the encapsulation data.
6. Writing Tests
To ensure our KEM encapsulation and decapsulation logic works correctly, we can write a unit test that verifies both parties. This test simulates a complete KEM key exchange, from key generation to secret verification.
Let’s create the test method:
@Test
void givenKem_whenSenderEncapsulatesAndReceiverDecapsulates_thenSecretsMatch() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X25519");
KeyPair keyPair = kpg.generateKeyPair();
KemUtils.KemResult senderResult = KemUtils.encapsulate("DHKEM", keyPair.getPublic());
assertNotNull(senderResult.sharedSecret());
assertNotNull(senderResult.encapsulation());
KemUtils.KemResult receiverResult = KemUtils.decapsulate("DHKEM", keyPair.getPrivate(),
senderResult.encapsulation());
SecretKey senderSecret = senderResult.sharedSecret();
SecretKey receiverSecret = receiverResult.sharedSecret();
assertArrayEquals(senderSecret.getEncoded(), receiverSecret.getEncoded(),
"Shared secrets from sender and receiver must match");
}
The test begins by generating the “X25519” key pair, representing the receiver’s public and private keys. The main test uses the “DHKEM” algorithm to simulate a full sender–receiver exchange: the sender encapsulates a shared secret using the receiver’s public key, and the receiver decapsulates it using their private key.
The test asserts that both the generated shared secret and encapsulation blob are non-null, and then verifies that the encoded bytes of the sender’s and receiver’s shared secrets are identical.
Now, let’s create a test case that verifies that the KEM decapsulation process fails when an incorrect private key is used:
@Test
void givenDifferentReceiverKey_whenDecapsulate_thenFails() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
KeyPair wrongKeyPair = kpg.generateKeyPair();
KemUtils.KemResult senderResult = KemUtils.encapsulate("DHKEM", keyPair.getPublic());
assertThrows(Exception.class, () ->
KemUtils.decapsulate("DHKEM", wrongKeyPair.getPrivate(), senderResult.encapsulation()));
}
It first generates a mismatched key pair (using “RSA” in this case) to simulate an unauthorized receiver. The test then performs encapsulation with the valid receiver’s public key, producing an encapsulation blob and shared secret.
When attempting to decapsulate this blob using the wrong private key, the operation is expected to fail, triggering an exception. The assertThrows assertion confirms this behavior, validating that KEM enforces strict cryptographic binding between the encapsulated data and the correct key pair.
7. Conclusion
In this tutorial, we explored how to leverage Java 21’s KEM API. We demonstrated how to encapsulate a shared secret using the receiver’s public key. Then, we transmitted the encapsulation blob. Finally, on the receiver side, we decapsulated it to reconstruct the same secret.
Through unit tests, we verified that the KEM workflow correctly produces identical keys for the intended recipient while preventing unauthorized decapsulation with the wrong private key.
As always, the source code is available over on GitHub.















