Java (JSSE)
Is PQC enabled? — quick check
macOS / Linux / Windows
# 1) No-dependency check — identify this machine first.
uname -a 2>/dev/null || true
# 2) Dependency check — prompt before installing anything.
if ! command -v java >/dev/null 2>&1; then
echo 'Java/JDK was not found.'
printf 'Install or enable Java/JDK now? [y/N] '
read answer
case "$answer" in
[Yy]*) echo 'Install JDK 24+ from your package manager or vendor distribution, then rerun this snippet.' ;;
*) echo 'Skipping Java/JDK-based check.'; exit 1 ;;
esac
fi
# 3) Capability — offline. JDK 24+ ships native ML-KEM in SunJSSE.
java -version 2>&1
# 4) Liveness — confirm JSSE can complete a TLS 1.3 handshake. There is no
# public JSSE API for the negotiated TLS named group on any JDK, so the
# authoritative answer comes from local openssl below.
jshell -s - <<EOF
import javax.net.ssl.*;
var f = (SSLSocketFactory) SSLSocketFactory.getDefault();
try (var s = (SSLSocket) f.createSocket("example.com", 443)) {
s.startHandshake();
var sess = s.getSession();
System.out.println("proto=" + sess.getProtocol());
System.out.println("cipher=" + sess.getCipherSuite());
}
/exit
EOF
# 5) Authoritative named-group readout — still local, via openssl CLI.
# 1) No-dependency check — identify this machine first.
uname -a 2>/dev/null || true
# 2) Dependency check — prompt before installing anything.
if ! command -v openssl >/dev/null 2>&1; then
echo 'OpenSSL was not found. A local PQC proof needs OpenSSL 3.5+.'
printf 'Install OpenSSL now? [y/N] '
read answer
case "$answer" in
[Yy]*)
if command -v brew >/dev/null 2>&1; then brew install openssl@3
elif command -v apt-get >/dev/null 2>&1; then sudo apt-get update && sudo apt-get install -y openssl
elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y openssl
elif command -v yum >/dev/null 2>&1; then sudo yum install -y openssl
else echo 'No supported package manager found. Install OpenSSL 3.5+ and retry.'; exit 1
fi ;;
*) echo 'Install OpenSSL 3.5+ and retry for a local PQC proof.'; exit 1 ;;
esac
fi
OPENSSL=openssl
if command -v brew >/dev/null 2>&1; then
BREW_OPENSSL="$(brew --prefix openssl@3 2>/dev/null)/bin/openssl"
[ -x "$BREW_OPENSSL" ] && OPENSSL="$BREW_OPENSSL"
fi
$OPENSSL version
if ! $OPENSSL list -tls-groups 2>/dev/null | grep -qiE 'X25519MLKEM768|MLKEM|Kyber'; then
echo 'This OpenSSL does not advertise ML-KEM groups. Upgrade to OpenSSL 3.5+ or load oqsprovider, then retry.'
exit 1
fi
$OPENSSL s_client -connect example.com:443 -tls1_3 -groups X25519MLKEM768 </dev/null 2>&1 | grep -E 'Negotiated TLS1\.3 group|Server Temp Key' Expected when PQC is ON
openjdk 24 2025-03-18
proto=TLSv1.3
cipher=TLS_AES_256_GCM_SHA384
Negotiated TLS1.3 group: X25519MLKEM768 What you'll see when PQC is OFF
openjdk 21.0.4 2024-07-16
proto=TLSv1.3
cipher=TLS_AES_256_GCM_SHA384
# (no group line — JDK 21 cannot offer ML-KEM) To force JDK 24+ to OFFER ML-KEM as a JSSE client: java -Djdk.tls.namedGroups=x25519_mlkem768,x25519,secp256r1 … . Earlier JDKs need BouncyCastle 1.79+ or upgrade.
JEP 496 (JDK 24, March 2025) added ML-KEM as a first-class JCA
algorithm. JEP 497 (JDK 24) added ML-DSA for signatures. JSSE wires
ML-KEM into TLS 1.3 as the X25519MLKEM768 hybrid group.
Pin named groups
java -Djdk.tls.namedGroups=X25519MLKEM768,X25519,secp256r1 -jar app.jar
You can also set this in java.security:
jdk.tls.namedGroups=X25519MLKEM768,X25519,secp256r1 Programmatic
SSLParameters params = ctx.getDefaultSSLParameters();
params.setNamedGroups(new String[]{"X25519MLKEM768","X25519","secp256r1"}); Older JDKs
- JDK 21 LTS — no ML-KEM in core. Add BouncyCastle 1.78+ as a JSSE provider for PQC support, or upgrade.
- JDK 17 LTS — same; BouncyCastle PQC + the BCJSSE provider.
- JDK 11 — too old; upgrade.
BouncyCastle setup (pre-24)
Security.insertProviderAt(new BouncyCastlePQCProvider(), 1);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 2); Spring / Tomcat / Netty
Spring Boot's embedded Tomcat reads server.ssl.* and the JVM-level
jdk.tls.namedGroups. Netty 4.2 picks up named groups from the JDK
automatically; for the BoringSSL backend it inherits Chrome's behavior.