Pular para o conteúdo
ecdsa.com
← Todos os artigos

PKIX path building failed no Java: causas e soluções reais

O Java não confia no que seu navegador confia. Ele usa o próprio trust store, não busca intermediários ausentes por padrão e diz a mesma coisa para qualquer falha. Cada causa abaixo foi reproduzida no JDK 25, com o comando que mostra qual delas é a sua.

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:

JDK 25.0.4.1, HttpClient contra um servidor com uma CA privada
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çãoResultado
1CA privada, cacerts padrão do JDKPKIX path building failed
2Raiz importada em uma cópia do cacerts, o servidor envia o certificado folha e o intermediárioHTTP 200
3Mesmo trust store, o servidor envia só o certificado folhaPKIX path building failed
4Como no 3, com -Dcom.sun.security.enableAIAcaIssuers=truePKIX path building failed
4bComo no 4, mais -Dcom.sun.security.allowedAIALocations=http://aia.example.test:8080/HTTP 200
5O servidor escolhe o certificado pelo SNI, o cliente envia SNIHTTP 200
6Mesmo servidor, -Djsse.enableSNIExtension=falsePKIX path building failed
7Mesmo 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 -showcerts: o servidor na porta 8443 envia a cadeia, o da 8444 só o certificado folha
$ 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 -cacerts edita o trust store do JDK que executa o keytool, e só esse.
  • Um trust store substituído. -Djavax.net.ssl.trustStore=only-corp.p12 substitui o cacerts, não acrescenta nada a ele. Um repositório que contém só a raiz da empresa quebra todos os sites públicos:
JDK 25: um trust store só com a raiz da empresa, conectando-se a letsencrypt.org
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.trustStore não existir, o JDK 25 não falha. Ele volta silenciosamente para o cacerts, e sua raiz simplesmente não está lá. Só a saída de depuração do trust manager mostra isso:
JDK 25: -Djavax.net.ssl.trustStore=/tmp/app-trustore.p12 (com erro de digitação), -Djavax.net.debug=ssl:trustmanager
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:

JDK 25 com a ISRG Root X1 e a X2 removidas de uma cópia do cacerts
--- 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=false está definido, muitas vezes como resto de um contorno antigo;
  • uma SSLSocketFactory personalizada 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:

O que o servidor envia e quem assinou cada certificado
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:

Depuração do JSSE, filtrada
java -Djavax.net.debug=ssl:handshake:verbose -jar app.jar 2>&1 \
  | grep -E 'trustStore is|Consuming server Certificate|"subject"|"issuer"|PKIX'
Linhas principais da saída de depuração do JSSE (JDK 25, CA privada, trecho)
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: a raiz está no repositório que a JVM carregou?
$ 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:

Um trust store da aplicação: raízes do JDK mais a raiz da empresa
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:

Java: um SSLContext para um único HttpClient
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/:

docker run -e USE_SYSTEM_CA_CERTS=1 -v ./certs:/certificates/ eclipse-temurin:25.0.4.1_1-jdk-noble (trecho)
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

  1. openssl s_client -showcerts a partir do host que falha: a cadeia está completa, e quem é o emissor do topo?
  2. -Djavax.net.debug=ssl:handshake:verbose: qual arquivo a JVM carregou, segundo trustStore is:?
  3. keytool -list nesse arquivo: a raiz emissora está lá?
  4. Há um proxy com inspeção de TLS no caminho? Então é da raiz dele que você precisa.
  5. O SNI é enviado? Verifique literais IP, jsse.enableSNIExtension e socket factories personalizadas.
  6. 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.security distribuí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_CERTS do 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.