javax.net.ssl.SSLHandshakeException は、TLS の最初のやりとり(ハンドシェイク:使う版・暗号・証明書を決める手順)が失敗したときの例外です。 原因はメッセージでほぼ決まります。まず、Received fatal alert があるかどうかを見ます。

メッセージ 断ったのは 主な原因
Received fatal alert: protocol_version 相手(サーバー) サーバーが TLS 1.0・1.1 しか使えない
Received fatal alert: handshake_failure 相手 共通の暗号スイートが無い。TLS 1.2 でクライアント証明書を出していない
Received fatal alert: certificate_required 相手 TLS 1.3 でクライアント証明書を出していない
Received fatal alert: unrecognized_name 相手 SNI(接続先の名前)がサーバーの設定と違う
No appropriate protocol (protocol is disabled or cipher suites are inappropriate) 自分(Java) Java 側で使える TLS の版が無い
No subject alternative DNS name matching 〈名前〉 found. 自分 証明書の名前(SAN)が接続先と違う
No subject alternative names matching IP address 〈IP〉 found 自分 IP で接続したが、証明書に IP の SAN が無い
No subject alternative names present 自分 IP で接続したが、証明書に SAN が1つも無い
No name matching 〈名前〉 found 自分 証明書に SAN が無く、CN(名前の欄)も違う
PKIX path validation failed: … validity check failed 自分 証明書の期限切れ(またはまだ有効でない)
PKIX path building failed: … unable to find valid certification path 自分 自己署名・社内 CA など、信頼する CA に無い証明書

Received fatal alert: … は「相手から断られた」、それ以外は「Java が断った」という意味です。 相手から断られたときは、理由が相手側のログにしか残らないことが多いので、下の -Djavax.net.debug で自分が何を送ったかを確かめます。

確認環境:OpenJDK 1.8.0_504・11.0.32.1・17.0.20.1・21.0.10・25.0.4.1(Linux)。OpenSSL 3.0.13 の openssl s_server で、条件を変えた TLS サーバーを12通り立て、Java の SSLSocket(ホスト名の検証あり)で接続してメッセージを集めました。HttpsURLConnection と java.net.http.HttpClient でも、Java 21 で同じメッセージになることを確かめています。Java がサーバー側の場合は、Java 8・21 の SSLServerSocket に openssl s_client から接続して確かめました(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「SSLHandshakeExceptionの直し方|alert別の原因表」

メッセージの読み方

Java 17 以降は、先頭に(アラート名)が付く

同じ失敗でも、Java 17 からメッセージの先頭に括弧付きのアラート名が付きました。

版 メッセージ
Java 8・11 Received fatal alert: handshake_failure
Java 17・21・25 (handshake_failure) Received fatal alert: handshake_failure
Java 8・11 No subject alternative DNS name matching app.example.test found.
Java 17・21・25 (certificate_unknown) No subject alternative DNS name matching app.example.test found.

Received が無いのに括弧がある場合、括弧の中は「Java が相手に送ったアラート」です。 上の2つ目は、Java が証明書を受け入れず、certificate_unknown を送って切断したことを表します。

原因は getCause() の奥にある

証明書の問題では、SSLHandshakeException の中に原因が何段も入っています。期限切れの例(Java 21)です。

javax.net.ssl.SSLHandshakeException: (certificate_expired) PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
  cause: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
  cause: java.security.cert.CertPathValidatorException: validity check failed
  cause: java.security.cert.CertificateExpiredException: NotAfter: Wed Jan 01 09:00:00 JST 2025

いちばん奥の CertificateExpiredException: NotAfter: … に、証明書の期限の日時が出ます。 ログに Caused by まで出しておくと、原因がすぐ分かります。

原因別のメッセージ一覧(Java 8〜25 の実行結果)

openssl s_server の設定と、Java の結果です。Java 17・21・25 は、先頭の(アラート名)以外は Java 8・11 と同じ文面でした。

TLS の版・暗号スイート

サーバーの設定 Java 8 Java 11 Java 17・21・25
制限なし 成功(TLSv1.3) 成功(TLSv1.3) 成功(TLSv1.3)
TLS 1.0・1.1 だけ(-no_tls1_2 -no_tls1_3) Received fatal alert: protocol_version 同左 (protocol_version) Received fatal alert: protocol_version
Java 側を TLSv1.1 だけにした(サーバーは制限なし) No appropriate protocol (protocol is disabled or cipher suites are inappropriate) 同左 同左
TLS 1.2・DHE-RSA-CAMELLIA128-SHA だけ Received fatal alert: handshake_failure 同左 (handshake_failure) Received fatal alert: handshake_failure
TLS 1.2・ECDHE-RSA-CHACHA20-POLY1305 だけ Received fatal alert: handshake_failure 成功 成功
TLS 1.2・AES128-GCM-SHA256(TLS_RSA)だけ 成功 Received fatal alert: handshake_failure (handshake_failure) Received fatal alert: handshake_failure
  • protocol_version は「サーバーが TLS 1.2 より前の版しか使えない」ときに、サーバーから返ってきました
  • No appropriate protocol は、Java 自身が使える版を持っていないときに、通信の前に出ました。TLS 1.0・1.1 は、確かめた5つの版すべてで既定で無効でした(下の jdk.tls.disabledAlgorithms)
  • 同じサーバーでも、Java の版で結果が逆になる組み合わせがありました。 ChaCha20 は Java 8u504 では使えず、TLS_RSA(鍵交換に RSA を使う暗号スイート)は Java 11.0.32.1 以降で無効になっていました

証明書

状況 メッセージ(Java 8・11。17 以降は先頭に括弧付きのアラート名) 17 以降の括弧
SAN が別の名前(other.example.test) No subject alternative DNS name matching app.example.test found. (certificate_unknown)
IP で接続、SAN に IP が無い No subject alternative names matching IP address 127.0.0.1 found (certificate_unknown)
IP で接続、SAN が1つも無い No subject alternative names present (certificate_unknown)
SAN が無く、CN も違う No name matching foo.example.test found (certificate_unknown)
期限切れ PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed (certificate_expired)
自己署名(信頼していない) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target (certificate_unknown)

SAN(Subject Alternative Name)は、証明書の中の「この証明書が使える名前の一覧」です。 確かめた5つの版では、SAN が無く CN が接続先と同じ証明書は、名前で接続すると成功しました。 SAN が無い証明書は、Java では通っても、他のクライアント(ブラウザなど)では通らないことがあります。証明書を作り直すときは SAN を入れます。

# SAN 付きの証明書要求を作る例(OpenSSL 3 系)
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr \
  -subj "/CN=app.example.test" \
  -addext "subjectAltName=DNS:app.example.test,IP:10.0.0.5"

# サーバーが返す証明書の SAN と期限を確かめる
openssl s_client -connect app.example.test:443 -servername app.example.test </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -ext subjectAltName -dates

自己署名や社内 CA の証明書で出る PKIX path building failed は、関連記事「PKIX path building failedの原因と直し方|Java証明書」で扱っています。openssl の使い方は、関連記事「opensslコマンドの使い方|目的別一覧と実行結果・エラー対処【3系】」で扱っています。

クライアント証明書・SNI

サーバーの設定 Java 8・11 Java 17・21・25
クライアント証明書を必須(TLS 1.3)、Java は出さない Received fatal alert: certificate_required (certificate_required) Received fatal alert: certificate_required
クライアント証明書を必須(TLS 1.3)、Java が出す 成功 成功
クライアント証明書を必須(TLS 1.2)、Java は出さない Received fatal alert: handshake_failure (handshake_failure) Received fatal alert: handshake_failure
SNI で app.example.test 以外を拒否、Java が wrong.example.test を送る Received fatal alert: unrecognized_name (unrecognized_name) Received fatal alert: unrecognized_name
SNI で証明書を切り替え、Java が SNI を送らない No subject alternative DNS name matching app.example.test found. (certificate_unknown) +同じ文面
  • クライアント証明書の不足は、TLS 1.3 なら certificate_required、TLS 1.2 なら handshake_failure になりました。 TLS 1.2 では、暗号スイートの不一致と同じアラートなので、見分けがつきません
  • SNI(Server Name Indication)は、接続先のホスト名をサーバーに伝えるしくみです。 1つの IP で複数のサイトを動かしているサーバーは、SNI を見て証明書を選びます。SNI を送らないと、別のサイトの証明書が返ってきて、名前違いのエラーになりました(-Djsse.enableSNIExtension=false で再現)

クライアント証明書を Java から出すには、PKCS12 のキーストアを指定します。

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

Java がサーバーのとき

Java の SSLServerSocket に openssl s_client から接続したときの、Java(サーバー)側の例外です。

状況 Java 8 Java 21
共通の暗号スイートが無い no cipher suites in common (handshake_failure) no cipher suites in common
TLS 1.3 だけにしたサーバーに TLS 1.2 で接続 Client requested protocol TLSv1.2 is not enabled or supported in server context (protocol_version) +同じ文面
クライアント証明書を必須にし、相手が出さない Empty client certificate chain (certificate_required) Empty client certificate chain

接続した openssl s_client 側には、sslv3 alert handshake failure(アラート番号 40)・tlsv1 alert protocol version(アラート番号 70)と出ました。クライアントで handshake_failure を受け取ったら、サーバー側のログに no cipher suites in common などの本当の理由が出ていないかを確かめます。

デバッグ出力(javax.net.debug)の読み方

java -Djavax.net.debug=ssl:handshake -jar app.jar 2> tls-debug.log

Java 21 で、暗号スイートが合わないサーバー(DHE-RSA-CAMELLIA128-SHA だけ)に接続したときは、399行出ました。Java 8 では 84行でした。見るところは4か所です。

① 自分が送った ClientHello(送った版・暗号スイート・SNI)

javax.net.ssl|DEBUG|10|main|…|ClientHello.java:638|Produced ClientHello handshake message (
"ClientHello": {
  "client version"      : "TLSv1.2",
  "cipher suites"       : "[TLS_AES_256_GCM_SHA384(0x1302), TLS_AES_128_GCM_SHA256(0x1301), …]",
  "extensions"          : [
    "server_name (0)": {
      type=host_name (0), value=app.example.test
    },
    …
    "supported_versions (43)": {
      "versions": [TLSv1.3, TLSv1.2]
    },

"client version": "TLSv1.2" は TLS 1.3 でも同じ値です。 実際に使える版は supported_versions の一覧で見ます。server_name が無ければ、SNI を送っていません。

② 相手から返ってきたアラート

javax.net.ssl|DEBUG|10|main|…|Alert.java:232|Received alert message (
"Alert": {
  "level"      : "fatal",
  "description": "handshake_failure"
}
)

ServerHello(サーバーの返事)が出る前にアラートが来ていれば、サーバーは ClientHello の中身(版・暗号スイート)を受け入れられなかったと読めます。

③ Java が使わなかった暗号スイート

…|SSLContextImpl.java:388|Ignore disabled cipher suite: TLS_RSA_WITH_AES_128_GCM_SHA256

Ignore disabled cipher suite は、jdk.tls.disabledAlgorithms などで無効にされていて、送らなかった暗号スイートです。 サーバーが使える暗号スイートがここに並んでいれば、原因は Java 側の設定です。

④ 最後のエラー

javax.net.ssl|ERROR|10|main|…|TransportContext.java:375|Fatal (HANDSHAKE_FAILURE): Received fatal alert: handshake_failure (

Java 8 では、ログの書式が javax.net.ssl|FINE|…・javax.net.ssl|SEVERE|… のように、レベル名が違いました。

出す量を変える

-Djavax.net.debug=help で出した一覧(Java 21)の主なものです。

指定 出るもの
ssl:handshake ハンドシェイクのメッセージ(まずこれ)
ssl:handshake:verbose ハンドシェイクを詳しく
ssl:trustmanager 証明書の検証(信頼する証明書の一覧など)
ssl:keymanager 自分の証明書(クライアント証明書)の選択
ssl:record TLS のレコード(通信の単位)ごと
all すべて(量が多い)

jdk.tls.disabledAlgorithms の確認方法

Java が使わない TLS の版・暗号は、java.security ファイルの jdk.tls.disabledAlgorithms で決まっています。 5つの版の値です(同じプログラムで Security.getProperty("jdk.tls.disabledAlgorithms") を表示)。

版 TLSv1・TLSv1.1 TLS_RSA_* SHA-1 署名(ハンドシェイク)
OpenJDK 1.8.0_504 無効 有効 有効
OpenJDK 11.0.32.1 無効 無効 無効
OpenJDK 17.0.20.1 無効 無効 無効
OpenJDK 21.0.10 無効 無効 無効
OpenJDK 25.0.4.1 無効 無効 無効

Java 21 の値です。

SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, ECDH, TLS_RSA_*, rsa_pkcs1_sha1 usage HandshakeSignature, ecdsa_sha1 usage HandshakeSignature, dsa_sha1 usage HandshakeSignature

OpenJDK のバグ管理では、TLS 1.0・1.1 の無効化は JDK-8202343(8u291・11.0.11 などにバックポート)、TLS_RSA の無効化は JDK-8245545 です。同じ「Java 11」「Java 8」でも、更新版(アップデート)の番号で値が違います。 本番と手元で結果が違うときは、まずこの値を比べます。

値を確かめる3つの方法

# 1. ファイルを見る(Java 9 以降は conf/security、Java 8 は jre/lib/security)
grep -A3 '^jdk.tls.disabledAlgorithms' "$JAVA_HOME/conf/security/java.security"
grep -A3 '^jdk.tls.disabledAlgorithms' "$JAVA_HOME/jre/lib/security/java.security"

# 2. 実際に使える版と暗号スイートを表示(確かめた版では 17 以降で使えた)
java -XshowSettings:security:tls -version
// 3. プログラムから表示(全版)
System.out.println(java.security.Security.getProperty("jdk.tls.disabledAlgorithms"));

Java 21 の -XshowSettings:security:tls は、Enabled Protocols に TLSv1.3・TLSv1.2 を、Enabled Cipher Suites に使える暗号スイートを表示しました。Linux のパッケージで入れた JDK では、java.security が /etc/java-21-openjdk/security/java.security などへのリンクになっていることがあります。

TLS 1.0・1.1 しか使えない相手に、一時的につなぐ

相手のサーバーを直せないときの回避策として、そのアプリだけで jdk.tls.disabledAlgorithms を上書きできます。JDK 全体の java.security を書き換えるより、影響を小さくできます。

# legacy.security(TLSv1・TLSv1.1・TLS_RSA_* などを外した値)
jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
java -Djava.security.properties=legacy.security -jar app.jar

TLS 1.0・1.1 だけのサーバーに、この設定で Java 8・11・21・25 から接続すると、TLSv1.1・TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA で接続できました。-Djava.security.properties が効くのは、java.security の security.overridePropertiesFile=true(Java 21 の既定)のときです。TLS 1.0・1.1 は安全でないため無効になっています。 回避策は期限を決めて使い、相手の TLS 1.2 以上への対応を進めます。

原因別の直し方

メッセージ 直し方
protocol_version 相手を TLS 1.2 以上に対応させる。急ぎなら上の一時回避
No appropriate protocol jdk.tls.client.protocols・https.protocols・setEnabledProtocols で TLS 1.2 以上を外していないか確かめる
handshake_failure(版は合っている) debug 出力の ClientHello の暗号スイートと、サーバーの設定を比べる。サーバー側のログも見る
handshake_failure(TLS 1.2・mTLS) クライアント証明書のキーストアを指定する
certificate_required 同上(TLS 1.3)
unrecognized_name 接続先の名前(URL のホスト名)を、サーバーに設定された名前にする
No subject alternative …・No name matching … 証明書の SAN に接続先の名前・IP を入れる。または、証明書にある名前で接続する
validity check failed 証明書を更新する。サーバーとクライアントの時刻も確かめる
PKIX path building failed 信頼する CA をトラストストアに入れる。中間証明書の付け忘れも確かめる

ホスト名の検証を無効にする(すべての証明書を信頼する)コードは、名前違いのエラーは消えますが、通信相手のなりすましを防げなくなります。 証明書か接続先の名前を直すのが本来の直し方です。証明書の自動更新は、関連記事「Let's Encrypt certbot自動更新|45日化とnginx設定手順」で扱っています。

よくある質問

Q. curl ではつながるのに、Java ではつながりません。 A. 使える TLS の版・暗号スイート・信頼する CA の一覧が、curl(OpenSSL)と Java で別々です。確かめた範囲では、TLS_RSA だけのサーバーには Java 8u504 だけがつながり、ChaCha20 だけのサーバーには Java 8u504 だけがつながりませんでした。-Djavax.net.debug=ssl:handshake で Java が送った内容を見ます。

Q. Java の版を上げたら、急に handshake_failure が出ました。 A. 新しい版で jdk.tls.disabledAlgorithms に追加された暗号(TLS_RSA など)しか、相手が使えない可能性があります。上の表のように、版ごとの値を比べます。

Q. タイムアウトや接続拒否と、どう見分けますか。 A. SSLHandshakeException は、TCP の接続はできたあとの失敗です。接続そのものができないときは ConnectException や SocketTimeoutException になります。こちらは関連記事「SocketTimeoutExceptionの直し方|Read/connectの違い」で扱っています。HTTP クライアントの使い方は、関連記事「Java HttpClientの使い方|GET・POSTでJSON送信」で扱っています。

まとめ

  • Received fatal alert: … は相手が断った、それ以外は Java が断った
  • Java 17 以降は、メッセージの先頭に (アラート名) が付く
  • protocol_version は相手が TLS 1.0・1.1 だけ、No appropriate protocol は Java 側で使える版が無い
  • handshake_failure は暗号スイートの不一致か、TLS 1.2 でのクライアント証明書の不足。TLS 1.3 では certificate_required
  • 名前違いは No subject alternative …・No name matching …。証明書の SAN を確かめる
  • -Djavax.net.debug=ssl:handshake で ClientHello・アラート・無視した暗号スイートを見る
  • jdk.tls.disabledAlgorithms は更新版ごとに違う。-XshowSettings:security:tls(17 以降)か Security.getProperty で確かめる

参考・出典

確認日はいずれも 2026年10月13日です。

  • Java SE 21 API「SSLHandshakeException」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/javax/net/ssl/SSLHandshakeException.html
  • Java SE 21「Java Secure Socket Extension (JSSE) Reference Guide」:https://docs.oracle.com/en/java/javase/21/security/java-secure-socket-extension-jsse-reference-guide.html
  • Java SE 21「Troubleshooting Security」:https://docs.oracle.com/en/java/javase/21/security/troubleshooting-security.html
  • OpenJDK「JDK-8202343 Disable TLS 1.0 and 1.1」:https://bugs.openjdk.org/browse/JDK-8202343
  • OpenJDK「JDK-8245545 Disable TLS_RSA cipher suites」:https://bugs.openjdk.org/browse/JDK-8245545
  • OpenSSL 3.0「openssl-s_server」:https://docs.openssl.org/3.0/man1/openssl-s_server/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「SSLHandshakeExceptionの直し方|alert別の原因表」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?