# Java SSLHandshakeException: handshake_failure explained

> Received fatal alert: handshake_failure means client and server could not agree on TLS parameters. Check protocols, cipher suites, SNI and client certificates.

Source: https://ssl.sh/errors/java-sslhandshakeexception-handshake-failure/
Last updated: 2026-11-10

**`SSLHandshakeException: Received fatal alert: handshake_failure` means the server ended the TLS handshake because it could not find settings both sides accept.** It is not a certificate-trust error: the usual causes are no shared protocol or cipher suite, a missing SNI hostname, or a server that demands a client certificate the Java client did not present.

## Quick fix

The handshake trace shows which parameter failed, so get it first:

```bash
java -Djavax.net.debug=ssl:handshake -jar app.jar 2>&1 | tee tls-debug.log
```

Then check what the server accepts from a neutral machine:

```bash
openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null
```

- Neither works: the server only offers old protocols (TLS 1.0/1.1) or ciphers the JDK disables. **Upgrade the server's TLS configuration**; the [TLS config generator](https://ssl.sh/tools/tls-config-generator/) gives a modern baseline.
- `openssl` works but Java fails: compare the ClientHello in the Java log with the server's suites, check SNI, and check whether the server asks for a client certificate (`Acceptable client certificate CA names` in the `openssl` output).

## Causes

### 1. No common protocol version or cipher suite

Current JDKs disable SSLv3, TLS 1.0, TLS 1.1 and weak algorithms such as RC4 and 3DES through `jdk.tls.disabledAlgorithms` in `$JAVA_HOME/conf/security/java.security`. A server limited to those fails immediately.

- **Confirm:** the [SSL checker](https://ssl.sh/tools/ssl-checker/) shows only TLS 1.0/1.1, or `openssl s_client -tls1_2` fails.
- **Fix:** enable TLS 1.2 and TLS 1.3 with ECDHE + AES-GCM or ChaCha20-Poly1305 suites on the server. Look up any suite name with the [cipher suite lookup](https://ssl.sh/tools/cipher-suite-lookup/).

### 2. Client explicitly restricted

The reverse also happens: an application sets `https.protocols=TLSv1`, `jdk.tls.client.protocols`, or a fixed `setEnabledCipherSuites` list from years ago, and the modern server rejects it.

- **Confirm:** the ClientHello in the debug log offers only old versions or a short suite list.
- **Fix:** remove the hard-coded protocol and cipher lists and let the JDK defaults apply.

### 3. SNI missing

Servers that host many names on one IP (CDNs, load balancers, Nginx with several `server` blocks) may abort when no `server_name` extension arrives.

- **Confirm:** the debug log's ClientHello has no `server_name` extension; or you connect by IP.
- **Fix:** connect by hostname, remove `-Djsse.enableSNIExtension=false`, and make sure custom `SSLSocketFactory` code passes the host name.

### 4. The server requires a client certificate

With [mutual TLS](https://ssl.sh/glossary/mtls/), a server can reject a client that sends no certificate or one from an untrusted CA.

- **Confirm:** `openssl s_client` lists acceptable client CA names; the Java log shows a `CertificateRequest` followed by an empty `Certificate` message.
- **Fix:** configure a keystore with the client certificate and key:

```bash
java -Djavax.net.ssl.keyStore=client.p12 -Djavax.net.ssl.keyStoreType=PKCS12 \
     -Djavax.net.ssl.keyStorePassword=secret -jar app.jar
```

If the client certificate came from a public CA, check [clientAuth EKU removal](https://ssl.sh/errors/client-auth-eku-removed/): public CAs stopped issuing certificates usable for client authentication in 2026.

### 5. Key type or signature algorithm mismatch

An ECDSA-only server and a client that disabled EC curves, or a server certificate signed with SHA-1, can also end in this alert. The debug log names the rejected algorithm.

## Fixes by environment

Find the side that restricts the handshake, then align protocols, ciphers, SNI or the client certificate there.

**Old Java 8 builds:** old Java 8 updates lack TLS 1.3 and may restrict strong AES keys. Update to a current Java 8 update or move to a supported LTS release.

**Spring Boot / Apache HttpClient / OkHttp:** these libraries use the JVM's JSSE settings unless you pass custom protocols or ciphers. Search the configuration for `TLSv1`, `SSLv3` or explicit cipher lists.

**Behind a load balancer:** the handshake may be terminated by the balancer, not the application server. Check the balancer's TLS policy rather than Tomcat's.

**Containers:** slim JRE images use the same `java.security` defaults; a handshake that fails in the container and works on your laptop usually means a different Java version, so print `java -version` in both.

## Don't do this

- **Removing TLS 1.0 and 1.1 from `jdk.tls.disabledAlgorithms` globally.** Every connection from that JVM becomes downgradable. If unavoidable, isolate the legacy call on a separate JVM and set a removal date.
- **Copying a cipher list from an old blog post.** Hard-coded lists age badly and cause the next handshake failure.
- **Installing a trust-all TrustManager.** It does nothing for handshake_failure and removes certificate checks.
