Error message
javax.net.ssl.SSLHandshakeException: (handshake_failure) Received fatal alert: handshake_failure
Also printed as
- javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
- Received fatal alert: handshake_failure
JDK 21 and later show the alert name in parentheses: "(handshake_failure) Received fatal alert: handshake_failure"; JDK 8 and 11 print "Received fatal alert: handshake_failure" only.
Java (JSSE) — TLS alert 40 received from the server
What it means
The server aborted the TLS handshake with alert 40 (handshake_failure). It found nothing acceptable in the JVM's ClientHello (no common cipher suite, group or signature algorithm), or it requires a client certificate the JVM did not present. Java only reports that the alert arrived; the reason is in the server's log or becomes visible with javax.net.debug.
Why it happens
The server only offers suites the JDK disabled
commonCurrent JDK updates (21.0.12 and 25.0.4 in our tests) list TLS_RSA_* (RSA key exchange), 3DES, RC4 and anonymous suites in jdk.tls.disabledAlgorithms. Legacy servers restricted to them fail with every up-to-date JVM.
Mutual TLS without a client certificate
commonThe server requested a certificate and the JVM had none (no javax.net.ssl.keyStore, or none issued by a CA the server accepts).
SNI missing or unknown
occasionalConnecting by IP, or with jsse.enableSNIExtension=false, makes CDNs and multi-tenant servers abort the handshake.
A very old JVM against a modern server
occasionalJava 7 or early Java 8 updates lack the ECDHE/GCM suites and TLS 1.2 defaults that hardened servers require.
How to fix it
- 1.
Look at the handshake
javax.net.debug=ssl:handshake prints the ClientHello (offered suites) and the alert; -XshowSettings:security:tls lists the JDK's enabled protocols and suites.
bash # Show what the JVM offers and how the handshake ends java -Djavax.net.debug=ssl:handshake -jar app.jar 2>&1 | grep -E 'ClientHello|cipher_suites|Alert|fatal' # Settings in effect (protocols, suites) for this JDK java -XshowSettings:security:tls -version - 2.
Server owners: enable current suites
TLS 1.3 and TLS 1.2 with ECDHE and AES-GCM or ChaCha20 work with every maintained JDK.
text # Server side: offer ECDHE + AEAD suites that current JDKs support ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; - 3.
Mutual TLS: configure the client key store
javax.net.ssl.keyStore (or an SSLContext built from a KeyManagerFactory) provides the client certificate.
bash # Mutual TLS: give the JVM the client certificate and key java -Djavax.net.ssl.keyStore=client.p12 -Djavax.net.ssl.keyStorePassword=changeit \ -Djavax.net.ssl.keyStoreType=PKCS12 -jar app.jar - 4.
Last resort: adjust disabledAlgorithms for one legacy client
An override file passed with -Djava.security.properties can remove TLS_RSA_* from jdk.tls.disabledAlgorithms for one JVM; copy the full default value and remove only what you need. These suites lack forward secrecy, so keep the exception narrow and temporary.
bash # Last resort for one legacy server: take this JDK's own value and drop only TLS_RSA_* v=$(sed -n '/^jdk.tls.disabledAlgorithms=/,/[^\\]$/p' "$JAVA_HOME/conf/security/java.security" | sed 's/\\$//' | tr -d '\n') printf '%s\n' "$v" | sed -E 's/, TLS_RSA_\*//' > legacy-rsa.security java -Djava.security.properties=legacy-rsa.security -jar legacy-client.jar
Related errors
No appropriate protocol (protocol is disabled or cipher suites are inappropriat…Java (JSSE)javax.net.ssl.SSLHandshakeException: (protocol_version) Received fatal alert: p…Java (JSSE)SSL routines:ssl3_read_bytes:ssl/tls alert handshake failureOpenSSL (client side)SSL routines:tls_post_process_client_hello:no shared cipherOpenSSL (server side: nginx, Apache, Postfix, openssl s_server)