En bref. PKIX path building failed: ... unable to find valid certification path to requested target signifie que la JVM n’a pas pu construire de chaîne allant du certificat du serveur à l’une des racines approuvées du magasin de confiance qu’elle a réellement chargé. Il s’agit en général de $JAVA_HOME/lib/security/cacerts, et non du magasin du système d’exploitation. Les causes habituelles sont une AC privée ou une AC de proxy absente de ce fichier, un serveur qui n’envoie pas son certificat intermédiaire, une JVM qui utilise un autre magasin de confiance que celui que vous croyez, un ancien JDK dépourvu d’une racine plus récente, et un nom SNI manquant. Déterminez laquelle vous concerne avec openssl s_client -showcerts et -Djavax.net.debug=ssl:handshake:verbose. Corrigez ensuite le magasin de confiance ou le serveur. N’installez jamais de gestionnaire de confiance (trust manager) qui accepte tout.
Ce que signifie le message
Sur les JDK actuels, l’exception complète se présente ainsi. Les JDK 21 et 25 la font précéder du nom de l’alerte TLS :
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Les éléments du message :
- PKIX désigne le modèle de chemins de certification X.509 défini par la RFC 5280. L’implémentation de Java se trouve dans
sun.security.provider.certpath. - La construction du chemin (path building) est la première de deux étapes. Java cherche une suite de certificats, quelle qu’elle soit, allant du certificat feuille du serveur jusqu’à une ancre de confiance. Ce n’est qu’en cas de succès que la validation du chemin (path validation) contrôle les dates, l’utilisation de la clé, les contraintes et la révocation. Les échecs de cette seconde étape ont leurs propres messages, par exemple PKIX path validation failed: validity check failed.
- « requested target » désigne le certificat du serveur.
Les frameworks enveloppent cette exception, par exemple la ResourceAccessException de Spring, le « Could not transfer artifact » de Maven ou le « Could not GET » de Gradle. Le texte PKIX reste présent dans la chaîne des causes : cherchez-le donc dans la trace de pile complète.
Les cinq causes
Les cinq ont toutes été reproduites avec eclipse-temurin:25.0.4.1_1-jdk-noble et un petit programme java.net.http.HttpClient. La PKI de test comprend une racine, une AC intermédiaire (« Issuing CA 1 ») et un certificat feuille pour api.example.test.
| # | Situation | Résultat |
|---|---|---|
| 1 | AC privée, cacerts par défaut du JDK | PKIX path building failed |
| 2 | Racine importée dans une copie de cacerts, le serveur envoie la feuille et l’intermédiaire | HTTP 200 |
| 3 | Même magasin de confiance, le serveur n’envoie que la feuille | PKIX path building failed |
| 4 | Comme 3, avec -Dcom.sun.security.enableAIAcaIssuers=true | PKIX path building failed |
| 4b | Comme 4, plus -Dcom.sun.security.allowedAIALocations=http://aia.example.test:8080/ | HTTP 200 |
| 5 | Le serveur choisit le certificat d’après le SNI, le client envoie le SNI | HTTP 200 |
| 6 | Même serveur, -Djsse.enableSNIExtension=false | PKIX path building failed |
| 7 | Même serveur, URL avec une adresse IP (pas de SNI) | PKIX path building failed |
1. Le certificat provient d’une AC à laquelle Java ne fait pas confiance
C’est le cas le plus fréquent : services internes, ingress Kubernetes, Nexus ou Artifactory dotés de certificats d’entreprise, bases de données, et tous les sites vus à travers un proxy d’inspection TLS, qui re-signe le trafic avec sa propre racine. Installer cette racine dans Windows ou macOS suffit aux navigateurs, mais sur la plupart des plateformes, Java ne lit pas le magasin du système d’exploitation. Le cacerts du JDK (118 entrées dans ce build Temurin 25) ignore tout de « Example Corp Root CA ».
2. Le serveur n’envoie pas son intermédiaire
Un serveur doit envoyer son certificat feuille et les intermédiaires jusqu’à la racine, celle-ci non comprise. Beaucoup de serveurs ne sont configurés qu’avec la feuille. Les navigateurs masquent souvent l’erreur, parce qu’ils gardent les intermédiaires en cache ou les récupèrent à l’URL indiquée dans l’extension Authority Information Access (AIA) du certificat. Par défaut, Java ne fait ni l’un ni l’autre. Les lignes 3 et 4 du tableau correspondent à cette situation. La racine est approuvée, mais le chemin ne peut pas être construit, parce que Java ne voit jamais « Issuing CA 1 ».
Deux lignes suffisent pour voir ce qu’envoie un serveur :
$ openssl s_client -connect api.example.test:8443 -servername api.example.test -showcerts </dev/null 2>/dev/null | grep -E '^ *[0-9]+ s:|^ *i:'
0 s:CN = api.example.test
i:CN = Example Corp Issuing CA 1
1 s:CN = Example Corp Issuing CA 1
i:CN = Example Corp Root CA
$ openssl s_client -connect api.example.test:8444 -servername api.example.test -showcerts </dev/null 2>/dev/null | grep -E '^ *[0-9]+ s:|^ *i:'
0 s:CN = api.example.test
i:CN = Example Corp Issuing CA 1
Si la dernière ligne i: désigne autre chose qu’une racine à laquelle vous faites confiance, il manque un intermédiaire. Le constructeur de chaînes de certificats accepte un bundle collé, le remet dans l’ordre, récupère les intermédiaires manquants via AIA et vous donne la chaîne exacte que le serveur devrait envoyer.
3. La JVM utilise un autre magasin de confiance que celui que vous croyez
Vous avez importé la racine, et l’échec persiste. Raisons courantes :
- Un autre JDK. Les IDE, les toolchains Gradle, les wrappers Maven, les serveurs d’applications et les images Docker embarquent leur propre JDK.
keytool -cacertsmodifie le magasin de confiance du JDK qui exécutekeytool, et seulement celui-là. - Un magasin de confiance remplacé.
-Djavax.net.ssl.trustStore=only-corp.p12remplacecacerts, il ne s’y ajoute pas. Un magasin qui ne contient que la racine de l’entreprise casse tous les sites publics :
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- Une faute de frappe dans le chemin du magasin de confiance. Si le fichier indiqué dans
javax.net.ssl.trustStoren’existe pas, le JDK 25 n’échoue pas. Il se rabat silencieusement surcacerts, si bien que votre racine n’y figure tout simplement pas. Seule la sortie de débogage du gestionnaire de confiance le signale :
Inaccessible trust store: /tmp/app-trustore.p12
trustStore is: /opt/java/openjdk/lib/security/cacerts
Fatal (CERTIFICATE_UNKNOWN): PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target (
Un magasin qui existe mais ne contient aucun certificat approuvé échoue autrement, avec l’erreur the trustAnchors parameter must be non-empty.
- Un magasin propre au framework. Les SSL bundles de Spring Boot, les connecteurs Tomcat, Kafka et les pilotes de bases de données ont leurs propres paramètres de magasin de confiance, et ceux-ci l’emportent sur les valeurs par défaut de la JVM pour leurs connexions.
La première ligne de la sortie de débogage JSSE établit quel fichier a été chargé (voir le diagnostic).
4. Le JDK est trop ancien pour la racine du serveur
Chaque mise à jour du JDK embarque un instantané des racines approuvées. Un site public dont la chaîne aboutit à une racine plus récente échoue sur un JDK antérieur à celle-ci. La page de compatibilité de Let’s Encrypt en donne un exemple : ISRG Root X2 est approuvée à partir de Java 8u401, 11.0.22, 17.0.10 et 21.0.2. Supprimer deux racines d’une copie de cacerts montre l’effet d’un magasin obsolète :
--- letsencrypt.org (chains to ISRG roots)
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
--- www.google.com (different CA)
HTTP 200
Si seuls certains sites publics échouent, regardez la racine à laquelle remonte leur chaîne, puis le niveau de mise à jour de votre JDK. Il en va de même pour un magasin de confiance copié depuis cacerts il y a des années et jamais rafraîchi depuis.
5. Pas de SNI, donc le serveur envoie un autre certificat
Un serveur qui héberge plusieurs noms choisit le certificat d’après l’extension Server Name Indication (SNI) du ClientHello. Sans SNI, il envoie un certificat par défaut, dans le test un default.invalid auto-signé. Ce certificat ne remonte à aucune racine, si bien que l’erreur est de nouveau PKIX (lignes 6 et 7). Java n’envoie pas de SNI lorsque :
- l’URL utilise une adresse IP (TLS interdit les adresses IP littérales dans le SNI),
-Djsse.enableSNIExtension=falseest défini, souvent un reste d’un ancien contournement,- une
SSLSocketFactorypersonnalisée crée des sockets sans nom d’hôte.
openssl s_client sans -servername montre ce que reçoit un tel client.
Diagnostic, étape par étape
1. Examinez la chaîne qu’envoie le serveur, si possible depuis la machine où se produit l’échec. Un proxy peut servir à cette machine un certificat différent :
openssl s_client -connect host.example:443 -servername host.example -showcerts </dev/null 2>/dev/null \
| grep -E '^ *[0-9]+ s:|^ *i:'
À travers un proxy d’inspection, l’émetteur au sommet est l’AC du proxy, et non une AC publique. Cela suffit à expliquer l’erreur.
2. Demandez à Java quel magasin de confiance il a chargé et ce qu’il a reçu. La sortie de débogage est longue, environ 5000 lignes pour un seul handshake échoué dans notre test. Filtrez-la donc :
java -Djavax.net.debug=ssl:handshake:verbose -jar app.jar 2>&1 \
| grep -E 'trustStore is|Consuming server Certificate|"subject"|"issuer"|PKIX'
trustStore is: /opt/java/openjdk/lib/security/cacerts
...
javax.net.ssl ... Consuming server Certificate handshake message (
"issuer" : "CN=Example Corp Issuing CA 1",
"subject" : "CN=api.example.test",
...
"issuer" : "CN=Example Corp Root CA",
"subject" : "CN=Example Corp Issuing CA 1",
...
javax.net.ssl ... Fatal (CERTIFICATE_UNKNOWN): PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target (
La première ligne nomme le fichier du magasin de confiance. Les blocs de certificat montrent la chaîne envoyée par le serveur, en commençant par la feuille ; l’émetteur du dernier bloc est le certificat que Java n’a pas pu relier à une racine. Si cet émetteur est une racine ou un intermédiaire que vous pensiez approuvé, vérifiez précisément ce fichier :
$ keytool -list -cacerts -storepass changeit -alias corp-root
keytool error: java.lang.Exception: Alias <corp-root> does not exist
Pour un autre fichier, utilisez keytool -list -keystore /path/to/store.p12. -v affiche le sujet et l’empreinte de chaque entrée, ce qui permet de comparer le certificat lui-même plutôt que l’alias.
3. Pour les détails de la construction du chemin, ajoutez -Djava.security.debug=certpath. Cette option journalise chaque émetteur candidat examiné par le constructeur de chemins et, pour AIA, indique si une récupération a été autorisée.
Des correctifs qui laissent la vérification activée
Corrigez d’abord le serveur lorsqu’un intermédiaire manque. Configurez la chaîne complète (la feuille, puis les intermédiaires) dans nginx, Apache, le répartiteur de charge ou l’ingress. Tous les clients en profitent, pas seulement Java.
Utilisez un magasin de confiance applicatif qui ajoute votre racine lorsque l’AC est privée ou qu’un proxy re-signe le trafic. Partez d’une copie de cacerts, pour que les sites publics continuent de fonctionner :
cp "$JAVA_HOME/lib/security/cacerts" app-truststore.p12
keytool -importcert -noprompt -alias corp-root -file corp-root-ca.pem \
-keystore app-truststore.p12 -storepass changeit
java -Djavax.net.ssl.trustStore=app-truststore.p12 -Djavax.net.ssl.trustStorePassword=changeit -jar app.jar
Pour les outils que vous ne lancez pas vous-même, comme Maven, Gradle ou les tests exécutés depuis l’IDE, passez les mêmes propriétés via JAVA_TOOL_OPTIONS ou les options JVM propres à l’outil. keytool -importcert -cacerts, à l’inverse, modifie le magasin par défaut du JDK. Cela touche toutes les applications de ce JDK, mais la modification disparaît à la prochaine mise à niveau du JDK.
Donnez à un client son propre magasin de confiance lorsqu’une seule intégration a besoin de la racine supplémentaire. Cette variante a été exécutée sur le service privé et sur un site public :
KeyStore ts = KeyStore.getInstance("PKCS12");
try (InputStream in = Files.newInputStream(Path.of("app-truststore.p12"))) {
ts.load(in, "changeit".toCharArray());
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
SSLContext ssl = SSLContext.getInstance("TLS");
ssl.init(null, tmf.getTrustManagers(), null);
HttpClient client = HttpClient.newBuilder().sslContext(ssl).build();
// https://api.example.test:8443/ -> HTTP 200
// https://letsencrypt.org/ -> HTTP 200
OkHttp, Apache HttpClient, Netty, gRPC et les pilotes JDBC acceptent tous un SSLContext ou un TrustManager construit de la même manière.
Dans les conteneurs, les images Eclipse Temurin importent des AC supplémentaires au démarrage lorsque USE_SYSTEM_CA_CERTS est défini et que des fichiers *.crt sont montés dans /certificates/ :
Adding certificate with alias Example Corp Root CA to the JVM truststore
Your keystore contains 240 entries
Cela ne fonctionne qu’avec le point d’entrée par défaut de l’image. Avec d’autres images de base, exécutez keytool -importcert dans le Dockerfile.
Sur macOS avec le JDK 23 ou une version ultérieure, -Djavax.net.ssl.trustStoreType=KeychainStore-ROOT fait en sorte que Java utilise le trousseau des racines du système, si bien qu’une racine installée par votre service informatique est approuvée (JDK-8320362). Sous Windows, -Djavax.net.ssl.trustStoreType=Windows-ROOT fait de même avec le magasin des racines de Windows.
Mettez à jour le JDK lorsqu’une racine publique manque. Corriger cacerts à la main sur un ancien JDK vous prive de tous les autres correctifs de sécurité de la mise à jour.
Pourquoi « faire confiance à tous les certificats » n’est pas une solution
La réponse la plus répandue en ligne est un X509TrustManager dont la méthode checkServerTrusted ne fait rien, souvent accompagné d’un HostnameVerifier qui renvoie true. L’exception disparaît, mais TLS se réduit alors à un chiffrement sans aucune vérification de qui se trouve à l’autre bout. Quiconque peut intercepter la connexion peut présenter n’importe quel certificat, lire le trafic et le modifier : un réseau Wi-Fi hostile, un routeur compromis, un proxy mal configuré, un détournement DNS. Ce code a aussi tendance à survivre à son usage « temporaire » et à être recopié dans le projet suivant. Chacun des correctifs ci-dessus prend quelques minutes et laisse la vérification activée.
Il en va de même pour l’import du certificat du serveur lui-même comme ancre de confiance. Cela fonctionne jusqu’au prochain renouvellement, ce qui, avec la réduction de la durée de vie des certificats, signifie d’ici quelques semaines. Faites confiance à l’AC qui émet les certificats, pas au certificat.
Liste de contrôle
openssl s_client -showcertsdepuis l’hôte en échec : la chaîne est-elle complète, et qui est l’émetteur au sommet ?-Djavax.net.debug=ssl:handshake:verbose: quel fichiertrustStore is:la JVM a-t-elle chargé ?keytool -listsur ce fichier : la racine émettrice y figure-t-elle ?- Un proxy d’inspection TLS se trouve-t-il sur le chemin ? Alors c’est sa racine qu’il vous faut.
- Le SNI est-il envoyé ? Vérifiez les adresses IP littérales,
jsse.enableSNIExtensionet les fabriques de sockets personnalisées. - Le JDK est-il assez récent pour la racine du site ?
Sources
- RFC 5280 : validation du chemin de certification (§6).
- Oracle, notes de version du JDK 21.0.10 : règles de filtrage des URI dans l’extension Authority Info Access (
com.sun.security.allowedAIALocations). - Le fichier
java.securitylivré avec le JDK 25 ($JAVA_HOME/conf/security/java.security), section « X.509 AuthorityInfoAccess caIssuer URI Filtering ». - JDK-8320362 : chargement des certificats d’ancre de confiance depuis le keystore Keychain (
KeychainStore-ROOT, JDK 23). - Let’s Encrypt, Compatibilité des certificats.
- Images de conteneur Eclipse Temurin : l’option
USE_SYSTEM_CA_CERTSdu point d’entrée.
Tous les résultats de cette page ont été obtenus le 20 octobre 2026 dans eclipse-temurin:25.0.4.1_1-jdk-noble (OpenJDK 25.0.4.1, OpenSSL 3.0.13 pour les serveurs de test), avec des certificats de test générés dans le conteneur.