Resposta curta. PKIX path building failed: ... unable to find valid certification path to requested target significa que a JVM não conseguiu montar uma cadeia do certificado do servidor até alguma raiz confiável do trust store que ela de fato carregou. Em geral, esse arquivo é o $JAVA_HOME/lib/security/cacerts, e não o repositório do sistema operacional. As causas habituais são uma CA privada ou de proxy que não está nesse arquivo, um servidor que não envia seu certificado intermediário, uma JVM que usa um trust store diferente do que você imagina, um JDK antigo sem uma raiz mais nova e a falta do nome no SNI. Descubra qual é o seu caso com openssl s_client -showcerts e -Djavax.net.debug=ssl:handshake:verbose. Depois, corrija o trust store ou o servidor. Nunca instale um trust manager que aceite tudo.
O que a mensagem significa
Nos JDKs atuais, a exceção completa tem esta forma. Os JDKs 21 e 25 acrescentam o nome do alerta TLS como prefixo:
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Parte por parte:
- PKIX é o modelo de caminhos de certificação X.509 da RFC 5280. A implementação do Java fica em
sun.security.provider.certpath. - Path building (construção do caminho) é a primeira de duas etapas. O Java tenta encontrar alguma sequência de certificados que vá do certificado folha do servidor até uma âncora de confiança. Só se isso der certo, a path validation (validação do caminho) verifica datas, uso da chave (Key Usage), restrições e revogação. As falhas da segunda etapa têm mensagens próprias, por exemplo PKIX path validation failed: validity check failed.
- “requested target” é o certificado do servidor.
Os frameworks encapsulam essa exceção, por exemplo a ResourceAccessException do Spring, o “Could not transfer artifact” do Maven ou o “Could not GET” do Gradle. O texto PKIX continua na cadeia de causas, então procure por ele no stack trace completo.
As cinco causas
Todas as cinco foram reproduzidas com eclipse-temurin:25.0.4.1_1-jdk-noble e um pequeno programa com java.net.http.HttpClient. A PKI de teste tem uma raiz, um intermediário (“Issuing CA 1”) e um certificado folha para api.example.test.
| # | Situação | Resultado |
|---|---|---|
| 1 | CA privada, cacerts padrão do JDK | PKIX path building failed |
| 2 | Raiz importada em uma cópia do cacerts, o servidor envia o certificado folha e o intermediário | HTTP 200 |
| 3 | Mesmo trust store, o servidor envia só o certificado folha | PKIX path building failed |
| 4 | Como no 3, com -Dcom.sun.security.enableAIAcaIssuers=true | PKIX path building failed |
| 4b | Como no 4, mais -Dcom.sun.security.allowedAIALocations=http://aia.example.test:8080/ | HTTP 200 |
| 5 | O servidor escolhe o certificado pelo SNI, o cliente envia SNI | HTTP 200 |
| 6 | Mesmo servidor, -Djsse.enableSNIExtension=false | PKIX path building failed |
| 7 | Mesmo servidor, URL com um endereço IP (sem SNI) | PKIX path building failed |
1. O certificado vem de uma CA em que o Java não confia
Este é o caso mais comum: serviços internos, ingresses do Kubernetes, Nexus ou Artifactory com certificados da empresa, bancos de dados e todo site acessado através de um proxy com inspeção de TLS, que reassina o tráfego com a própria raiz. Instalar essa raiz no Windows ou no macOS deixa os navegadores satisfeitos, mas, na maioria das plataformas, o Java não lê o repositório do sistema operacional. O cacerts do JDK (118 entradas neste build do Temurin 25) não faz ideia de quem seja “Example Corp Root CA”.
2. O servidor não envia seu intermediário
Um servidor deve enviar seu certificado folha e os intermediários até a raiz, mas sem incluí-la. Muitos servidores são configurados só com o certificado folha. Os navegadores costumam esconder o erro, porque guardam intermediários em cache ou os buscam na URL da extensão Authority Information Access (AIA) do certificado. Por padrão, o Java não faz nenhuma das duas coisas. As linhas 3 e 4 da tabela mostram essa situação. A raiz é confiável, mas o caminho não pode ser montado, porque o Java nunca vê o “Issuing CA 1”.
Duas linhas bastam para ver o que um servidor envia:
$ 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
Se a última linha i: nomear algo que não seja uma raiz em que você confia, falta um intermediário. O construtor de cadeia de certificados recebe um bundle colado, coloca os certificados em ordem, busca os intermediários ausentes via AIA e entrega a cadeia exata que o servidor deveria enviar.
3. A JVM usa um trust store diferente do que você pensa
Você importou a raiz, e o erro continua. Motivos comuns:
- Outro JDK. IDEs, toolchains do Gradle, Maven wrappers, servidores de aplicação e imagens Docker trazem o próprio JDK.
keytool -cacertsedita o trust store do JDK que executa okeytool, e só esse. - Um trust store substituído.
-Djavax.net.ssl.trustStore=only-corp.p12substitui ocacerts, não acrescenta nada a ele. Um repositório que contém só a raiz da empresa quebra todos os sites públicos:
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- Um erro de digitação no caminho do trust store. Se o arquivo indicado em
javax.net.ssl.trustStorenão existir, o JDK 25 não falha. Ele volta silenciosamente para ocacerts, e sua raiz simplesmente não está lá. Só a saída de depuração do trust manager mostra isso:
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 (
Um repositório que existe, mas não contém nenhum certificado confiável, falha de outro jeito, com the trustAnchors parameter must be non-empty.
- Um repositório no nível do framework. Os SSL bundles do Spring Boot, os conectores do Tomcat, o Kafka e os drivers de banco de dados têm as próprias configurações de trust store, e elas prevalecem sobre os padrões da JVM nas conexões deles.
A primeira linha da saída de depuração do JSSE mostra qual arquivo foi carregado (veja o diagnóstico).
4. O JDK é antigo demais para a raiz do servidor
Cada atualização do JDK traz um retrato das raízes confiáveis daquele momento. Um site público cuja cadeia termina em uma raiz mais nova falha em um JDK anterior a ela. A página de compatibilidade da Let’s Encrypt dá um exemplo: a ISRG Root X2 é confiável a partir do Java 8u401, 11.0.22, 17.0.10 e 21.0.2. Remover duas raízes de uma cópia do cacerts mostra o que acontece com um repositório desatualizado:
--- 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
Se só alguns sites públicos falham, veja até qual raiz a cadeia deles chega e, depois, o nível de atualização do seu JDK. O mesmo acontece com um trust store que foi copiado do cacerts anos atrás e nunca foi atualizado.
5. Sem SNI, o servidor envia outro certificado
Um servidor que hospeda vários nomes escolhe o certificado pelo Server Name Indication (SNI) do ClientHello. Sem SNI, ele envia um certificado padrão, que no teste era um default.invalid autoassinado. Esse certificado não se encadeia a nada, então o erro volta a ser o PKIX (linhas 6 e 7). O Java não envia SNI quando:
- a URL usa um endereço IP (o TLS proíbe literais IP no SNI);
-Djsse.enableSNIExtension=falseestá definido, muitas vezes como resto de um contorno antigo;- uma
SSLSocketFactorypersonalizada cria sockets sem nome de host.
O openssl s_client sem -servername mostra o que um cliente assim recebe.
Diagnóstico passo a passo
1. Veja a cadeia que o servidor envia, se possível a partir da máquina em que ocorre a falha. Um proxy pode servir a essa máquina um certificado diferente:
openssl s_client -connect host.example:443 -servername host.example -showcerts </dev/null 2>/dev/null \
| grep -E '^ *[0-9]+ s:|^ *i:'
Através de um proxy com inspeção, o emissor do topo é a CA do proxy, e não uma CA pública. Isso, por si só, explica o erro.
2. Pergunte ao Java qual trust store ele carregou e o que recebeu. A saída de depuração é longa, cerca de 5.000 linhas para um único handshake com falha no nosso teste, então filtre-a:
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 (
A primeira linha indica o arquivo do trust store. Os blocos de certificado mostram a cadeia que o servidor enviou, começando pelo certificado folha; o emissor do último bloco é o certificado que o Java não conseguiu ligar a uma raiz. Se esse emissor for uma raiz ou um intermediário em que você esperava confiar, verifique exatamente esse arquivo:
$ keytool -list -cacerts -storepass changeit -alias corp-root
keytool error: java.lang.Exception: Alias <corp-root> does not exist
Para outro arquivo, use keytool -list -keystore /path/to/store.p12. A opção -v imprime o titular e a impressão digital de cada entrada, então você pode comparar o próprio certificado, e não o alias.
3. Para detalhes da construção do caminho, acrescente -Djava.security.debug=certpath. Com essa opção, o JDK registra cada emissor candidato que o construtor considerou e, no caso do AIA, se uma busca foi permitida.
Correções que mantêm a verificação ativa
Corrija primeiro o servidor quando faltar um intermediário. Configure a cadeia completa (o certificado folha e, depois, os intermediários) no nginx, no Apache, no balanceador de carga ou no ingress. Todos os clientes se beneficiam, não só o Java.
Use um trust store da aplicação que acrescente sua raiz quando a CA for privada ou um proxy reassinar o tráfego. Parta de uma cópia do cacerts, para que os sites públicos continuem funcionando:
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
Para ferramentas que você não inicia diretamente, como Maven, Gradle ou testes executados pela IDE, passe as mesmas propriedades por JAVA_TOOL_OPTIONS ou pelas opções de JVM da própria ferramenta. Já keytool -importcert -cacerts altera o repositório padrão do JDK. A mudança alcança todas as aplicações desse JDK, mas se perde na próxima atualização do JDK.
Dê a um único cliente o próprio trust store quando só uma integração precisar da raiz extra. Esta versão foi executada contra o serviço privado e um site público:
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
O OkHttp, o Apache HttpClient, o Netty, o gRPC e os drivers JDBC aceitam um SSLContext ou um TrustManager construído da mesma forma.
Em contêineres, as imagens Eclipse Temurin importam CAs extras na inicialização quando USE_SYSTEM_CA_CERTS está definida e há arquivos *.crt montados em /certificates/:
Adding certificate with alias Example Corp Root CA to the JVM truststore
Your keystore contains 240 entries
Isso só acontece pelo entrypoint padrão da imagem. Com outras imagens base, execute keytool -importcert no Dockerfile.
No macOS com JDK 23 ou posterior, -Djavax.net.ssl.trustStoreType=KeychainStore-ROOT faz o Java usar o chaveiro de raízes do sistema, e assim uma raiz instalada pelo seu departamento de TI passa a ser confiável (JDK-8320362). No Windows, -Djavax.net.ssl.trustStoreType=Windows-ROOT faz o mesmo com o repositório de raízes do Windows.
Atualize o JDK quando faltar uma raiz pública. Remendar o cacerts à mão em um JDK antigo deixa de fora todas as outras correções de segurança da atualização.
Por que “confiar em todos os certificados” não é uma correção
A resposta mais popular na internet é um X509TrustManager cujo checkServerTrusted não faz nada, muitas vezes junto com um HostnameVerifier que retorna true. Isso faz a exceção sumir ao transformar o TLS em criptografia em que ninguém do outro lado foi verificado. Qualquer um que consiga interceptar a conexão pode apresentar qualquer certificado, ler o tráfego e alterá-lo: uma rede Wi-Fi hostil, um roteador comprometido, um proxy mal configurado, um sequestro de DNS. Além disso, esse código costuma sobreviver ao seu propósito “temporário” e acaba copiado para o próximo projeto. Cada correção acima leva poucos minutos e mantém a verificação ativa.
O mesmo vale para importar o certificado do próprio servidor como âncora de confiança. Isso funciona até a próxima renovação, que, com vidas úteis mais curtas dos certificados, chega em questão de semanas. Confie na CA que emite os certificados, e não no certificado.
Lista de verificação
openssl s_client -showcertsa partir do host que falha: a cadeia está completa, e quem é o emissor do topo?-Djavax.net.debug=ssl:handshake:verbose: qual arquivo a JVM carregou, segundotrustStore is:?keytool -listnesse arquivo: a raiz emissora está lá?- Há um proxy com inspeção de TLS no caminho? Então é da raiz dele que você precisa.
- O SNI é enviado? Verifique literais IP,
jsse.enableSNIExtensione socket factories personalizadas. - O JDK é recente o bastante para a raiz do site?
Fontes
- RFC 5280: validação do caminho de certificação (§6).
- Oracle, notas de versão do JDK 21.0.10: regras de filtro para URIs na extensão Authority Info Access (
com.sun.security.allowedAIALocations). - O arquivo
java.securitydistribuído com o JDK 25 ($JAVA_HOME/conf/security/java.security), seção “X.509 AuthorityInfoAccess caIssuer URI Filtering”. - JDK-8320362: “Load anchor certificates from Keychain keystore” (
KeychainStore-ROOT, JDK 23). - Let’s Encrypt, Compatibilidade de certificados.
- Imagens de contêiner Eclipse Temurin: a opção
USE_SYSTEM_CA_CERTSdo entrypoint.
Todos os resultados desta página foram produzidos em 20 de outubro de 2026 em eclipse-temurin:25.0.4.1_1-jdk-noble (OpenJDK 25.0.4.1, OpenSSL 3.0.13 para os servidores de teste), com certificados de teste gerados no contêiner.