Mensagem de erro
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Também aparece como
- javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- unable to find valid certification path to requested target
Os JDKs 21 e 25 acrescentam o alerta TLS como prefixo: “javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: …”. Logs de JDKs mais antigos mostram “javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: …”. Frameworks e ferramentas de build (Spring, Maven, Gradle, IDEs) encapsulam o erro nas próprias mensagens, mas mantêm o texto PKIX na causa.
Java (JSSE, SunX509 trust manager) — javax.net.ssl.SSLHandshakeException caused by sun.security.validator.ValidatorException
O que significa
O construtor de caminhos de certificação PKIX do Java não conseguiu montar uma cadeia que leve do certificado do servidor a alguma âncora de confiança do trust store que a JVM está usando. O “requested target” é o certificado do servidor. O Java confia apenas no próprio arquivo cacerts (ou no repositório indicado com javax.net.ssl.trustStore), e não no repositório do sistema operacional; por isso, um certificado de uma CA da empresa, de um proxy com inspeção de TLS ou de um servidor que não envia o intermediário falha aqui.
Por que acontece
O certificado do servidor vem de uma CA privada
comumServiços internos, ingresses do Kubernetes, repositórios Nexus ou Artifactory e bancos de dados com certificados da empresa se encadeiam até uma raiz que não está no cacerts do JDK.
Um proxy com inspeção de TLS reassina a conexão
comumEm redes corporativas, o Maven Central, os portais de plugins do Gradle e as APIs públicas aparecem com certificados emitidos pela raiz do proxy. A raiz instalada no Windows ou no macOS não chega ao Java.
O servidor não envia o certificado intermediário
comumPor padrão, o Java não busca intermediários ausentes (com.sun.security.enableAIAcaIssuers vem desativado), então um servidor configurado só com o certificado folha falha, embora os navegadores o aceitem.
A JVM usa um trust store diferente do que você pensa
ocasionalIDEs e ferramentas de build trazem o próprio JDK, servidores de aplicação apontam javax.net.ssl.trustStore para um repositório com apenas algumas CAs, e contêineres podem usar um JRE cujo cacerts nunca foi atualizado. Uma raiz importada no cacerts de um JDK não ajuda outro JDK.
Um JDK muito antigo não tem as raízes públicas mais novas
raraBuilds do Java 8 sem atualização, anteriores à inclusão da raiz de uma CA no cacerts, falham em sites que usam essa raiz; as atualizações atuais já a trazem.
Como corrigir
- 1.
Ver a cadeia e o trust store em uso
O openssl s_client mostra o que o servidor envia. Com javax.net.debug=ssl:handshake:verbose, o Java imprime o caminho do trust store que carregou e cada certificado que recebeu.
bash openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # Or from Java itself: prints the chain and the trust store in use java -Djavax.net.debug=ssl:handshake:verbose -jar app.jar 2>&1 | grep -E 'trustStore is|Subject:|Issuer:' - 2.
Usar um trust store da aplicação que inclua sua raiz
Copie o cacerts do JDK (para que as CAs públicas continuem funcionando), importe a raiz da empresa ou do proxy com o keytool e aponte a aplicação para essa cópia com javax.net.ssl.trustStore e trustStorePassword (ou JAVA_TOOL_OPTIONS para ferramentas que não são iniciadas por você).
bash # A copy of the JDK's cacerts plus your root: public sites keep working 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 - 3.
Importar a raiz no cacerts do JDK
O comando keytool -cacerts atua sobre o repositório padrão do JDK que executa o keytool (senha “changeit” por padrão). Todas as aplicações desse JDK passam a confiar na raiz; a alteração se perde quando o JDK é substituído.
bash # Add the root to this JDK's default trust store (applies to every app on this JDK; # repeat after each JDK upgrade) sudo keytool -importcert -noprompt -cacerts -alias corp-root -file corp-root-ca.pem -storepass changeit - 4.
Contêineres: deixar a imagem importar sua CA
Quando USE_SYSTEM_CA_CERTS está definida, o entrypoint das imagens Eclipse Temurin adiciona os arquivos *.crt montados em /certificates ao cacerts da JVM na inicialização (isso só acontece com o entrypoint padrão da imagem). Outras imagens precisam da etapa do keytool no Dockerfile.
bash # Eclipse Temurin images: mount your CA(s) as *.crt and let the entrypoint add them to cacerts docker run -e USE_SYSTEM_CA_CERTS=1 \ -v "$PWD/certs:/certificates/" eclipse-temurin:25.0.4.1_1-jdk-noble java -jar /app/app.jar - 5.
Dar a um único cliente o próprio trust store
Crie um SSLContext a partir de um TrustManagerFactory inicializado com seu trust store e passe-o para o HttpClient (ou OkHttp, Apache HttpClient, Netty). Nunca instale um trust manager que aceite qualquer certificado.
java // One client with its own trust store (verification stays on) 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();
Erros relacionados
PKIX path validation failed: java.security.cert.CertPathValidatorException: val…Java (JSSE)java.security.InvalidAlgorithmParameterException: the trustAnchors parameter mu…Java (JSSE / PKIX)No subject alternative DNS name matching api.example.com found.Java (JSSE HostnameChecker)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)x509: certificate signed by unknown authorityGo crypto/x509