Security & Governance · recorded 2026-08-30 · applies to 2 tool(s) ✓ Verified

Problem: JDK old-project TLS upgrade fails with Received fatal alert: protocol_version / No appropriate protocol —— Trove problem library

Verified 2026-08-30TLS handshake fails after JDK upgrade — disable SSLv3/TLSv1/TLSv1.1 explicitly in jdk.tls.disabledAlgorithms (java.security) to force TLS1.2+.

Quick fix

# Disable legacy protocols globally on the client (edit java.security, most common fix)
# $JAVA_HOME/jre/lib/security/java.security (Oracle) or $JAVA_HOME/conf/security/java.security (OpenJDK)
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, MD5withRSA, DH keySize < 768

# Per-application JVM flags (-Dhttps.protocols only affects HttpsURLConnection,
# -Djdk.tls.client.protocols covers the whole client)
-Dhttps.protocols=TLSv1.2,TLSv1.3 -Djdk.tls.client.protocols=TLSv1.2,TLSv1.3

# Server side (nginx)
ssl_protocols TLSv1.2 TLSv1.3;

What you will run into

An old project (JDK 1.6/1.7/early 1.8) starts failing HTTPS calls after a dependency upgrade, server migration or enabling HTTPS:

  • Client throws javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version or No appropriate protocol (protocol is disabled or cipher suites are inappropriate);
  • openssl s_client -connect <host>:<port> -tls1 works but -tls1_2 fails (server only speaks legacy protocols); or the opposite - a new server rejects the old client.

Why this happens

TLS handshake requires a non-empty intersection between the client's and server's enabled protocol sets. When the intersection is empty the server sends alert protocol_version (alert 70), which surfaces in Java as SSLHandshakeException. Three typical traps:

TrapMechanism
Server only enables TLSv1.0/1.1New JDK/OpenSSL disables legacy protocols by default, client has nothing to negotiate
Old JDK defaults to TLSv1.0 maxJDK 1.7 and below do not enable TLS1.2 by default; cannot reach TLS1.2-only servers
Hardcoded legacy protocols in codesetEnabledProtocols("TLSv1"), SSLv3, custom SSLContext, disabled hostname verification

JDK defaults by version (summarized from the source article):

JDK versionDefault TLS capabilityKey point
1.6max TLSv1.0need a newer JDK for TLS1.2
1.7.0_79supports TLS1.2 but not enabled by defaultenable manually + disable TLSv1/1.1
8u112TLS1.2; no TLS1.3TLSv1/1.1 not disabled by default, disable manually
8u261~8u340TLS1.3 enabled for server role8u341+ also enabled for client role
11.0.0~11.0.10TLS1.2/1.3; TLSv1/1.1 not disabled11.0.11+ disables TLSv1/1.1 by default

The TLS handshake is "protocol-set intersection" negotiation: the client sends its supported protocol list, the server picks from the intersection; an empty intersection yields alert protocol_version (70), surfacing as SSLHandshakeException in Java.

The problem is always "one side's capability set is out of step with its era". The server only enables TLSv1.0/1.1, while modern clients disable them by default.

Or the client is too old — JDK ≤1.7 defaults to max TLSv1.0 and cannot reach servers that only support 1.2+. Code may also hard-code legacy protocols.

JDK defaults evolve over versions (8u112 supports 1.2 but not 1.3; 8u341+ enables 1.3 as client default; 11.0.11+ disables 1.0/1.1 by default).

Deterministic takeaway: on handshake failure, determine both sides' capabilities first. Probe the server with openssl s_client -tls1_2 -connect <host>:<port>; on the client, -Djavax.net.debug=ssl:handshake shows the sent protocol list.

Fix per side: enable newer protocols on the server, upgrade the JDK, or remove hard-coded protocols from code.

On old JDKs, TLS1.3 has known bugs (JDK-8211806 etc.) — avoid enabling it before 11.0.8.

Fixed diagnostic: alert 70 = empty protocol intersection, not a certificate or cipher problem.

Step by step

1. Find out which side is restricted — Required

# Three-way probe: 1.2 OK / 1.0, 1.1 rejected -> server is modern, the client is the problem
openssl s_client -connect <host>:<port> -tls1_2
# 1.0 OK / 1.2 rejected -> server still runs legacy TLS, upgrade the server first
openssl s_client -connect <host>:<port> -tls1

2. Client: disable legacy protocols (three scopes) — Required

  • Global (recommended): add TLSv1, TLSv1.1 to jdk.tls.disabledAlgorithms in java.security, restart apps; this affects every app on that JDK - apps that still need TLSv1/1.1 will break, confirm first.
  • JVM flags (current app): -Dhttps.protocols=TLSv1.2,TLSv1.3 -Djdk.tls.client.protocols=TLSv1.2,TLSv1.3.
  • Code (single connection): connection.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"}); Spring Boot: server.ssl.enabled-protocols: TLSv1.2, TLSv1.3.

3. Hunt down hardcoded protocols and disabled verification — Required

grep -rn "setHostnameVerifier" --include="*.java" .
grep -rn "TLSv1\|SSLv3" --include="*.java" .

Check: HttpsURLConnection.setEnabledProtocols, Apache HttpClient SSLConnectionSocketFactory protocol lists, custom RestTemplate SSL contexts; never ship a HostnameVerifier that skips certificate checks.

4. Verify — Required

openssl s_client -connect <host>:<port> -tls1_2   # should succeed
openssl s_client -connect <host>:<port> -tls1     # should fail
openssl s_client -connect <host>:<port> -tls1_1   # should fail

App logs show no SSLHandshakeException / NoSuchAlgorithmException and negotiate TLSv1.2.

How to confirm it works

The three-way probe shows 1.2 OK, 1.0/1.1 rejected, and the app handshake version is TLSv1.2+; Java no longer throws Received fatal alert: protocol_version.

Other environments

EnvironmentNotes
Server (nginx/openssl)ssl_protocols TLSv1.2 TLSv1.3;, equivalent to the "new server" scenario in the repro log
OpenSSL 3.5disables TLSv1.0/1.1 at the default security level, same mechanism as JDK 8u261+/11.0.11+
Not verifiedper-version JDK default tables come from the source article; no legacy JDK available locally

Symptom

HTTPS calls fail with SSLHandshakeException: Received fatal alert: protocol_version or No appropriate protocol; the openssl three-way probe is asymmetric between TLS1.0/1.1 and TLS1.2 (one side connects, the other does not).

Cause

The intersection of the client's and server's enabled TLS sets is empty: server only speaks TLSv1.0/1.1 while the client disables them by default; or an old JDK caps at TLS1.0 against a modern server; or code hardcodes legacy protocols.

Solution

  1. Use the openssl three-way probe (-tls1 / -tls1_1 / -tls1_2) to find which side is restricted;
  2. Server: nginx ssl_protocols TLSv1.2 TLSv1.3;, upgrade old openssl;
  3. Client: add TLSv1, TLSv1.1 to jdk.tls.disabledAlgorithms in java.security (global), or -Djdk.tls.client.protocols=TLSv1.2,TLSv1.3 (per-app), or setEnabledProtocols in code (per-connection);
  4. Find and remove TLSv1/SSLv3 hardcoding and HostnameVerifier that skips verification;
  5. Verify with the probe: 1.2 connects, 1.0/1.1 rejected.
Verification Evidence
  • Mechanism repro (2026-08-30, WSL Ubuntu, OpenSSL 3.5.5): local TLS1.0-only server -> TLS1.2 client handshake fails (unsupported protocol); TLS1.0 client succeeds. Reverse: TLS1.2-only server -> TLS1.0 client fails with tlsv1 alert protocol version (the underlying cause of Java's Received fatal alert: protocol_version), TLS1.2 client succeeds. local repro log 2026-08-30.
  • Not verified: no legacy JDK available locally; per-version default tables come from the source article ("The Long-Read TLS Protocol Upgrade Pitfall Handbook", Chinese original, 2026-08-26); OpenSSL 3.5 default security level disables TLS1.0/1.1, same mechanism as modern JDKs.

Related canonicals

Read in order: precondition -> same root / same domain / related:

Applicable tools (2)

This problem is linked from the tool pages below — reachable from either side.

jdkopenssl

Source & evidence

Recorded Verified ✓ VerifiedApplies to 2 tool(s)
Entries contain locally measured or reviewable-source solutions; unmeasured items are explicitly labeled.

Source: https://www.cnblogs.com/xsge/p/22703862

← Security & Governance domain | Problem Catalog Experience | Facing another issue? Go to the Helpdesk