Error message
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Also printed as
- 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
JDK 21 and 25 prefix the TLS alert: "javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: …". Logs from older JDKs show "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: …". Frameworks and build tools (Spring, Maven, Gradle, IDEs) wrap it in their own messages but keep the PKIX text in the cause.
Java (JSSE, SunX509 trust manager) — javax.net.ssl.SSLHandshakeException caused by sun.security.validator.ValidatorException
What it means
Java's PKIX certificate path builder could not construct a chain from the server's certificate to any trust anchor in the trust store the JVM is using. "Requested target" is the server's certificate. Java trusts only its own cacerts file (or the store given with javax.net.ssl.trustStore), not the operating system's store, so a certificate from a company CA, a TLS-inspecting proxy or a server without its intermediate fails here.
Why it happens
The server's certificate comes from a private CA
commonInternal services, Kubernetes ingresses, Nexus or Artifactory repositories and databases with company certificates chain to a root that is not in the JDK's cacerts.
A TLS-inspecting proxy re-signs the connection
commonOn company networks, Maven Central, Gradle plugin portals and public APIs are presented with certificates from the proxy's root. Installing that root in Windows or macOS does not reach Java.
The server does not send its intermediate certificate
commonJava does not fetch missing intermediates by default (com.sun.security.enableAIAcaIssuers is off), so a server configured with only its leaf fails even though browsers accept it.
The JVM uses another trust store than you think
occasionalIDEs and build tools bundle their own JDK, application servers set javax.net.ssl.trustStore to a store containing only a few CAs, and containers may use a JRE whose cacerts was never updated. A root imported into one JDK's cacerts does not help another JDK.
A very old JDK lacks newer public roots
rareUnpatched Java 8 builds from before a CA's root was added to cacerts fail on sites using that root; current updates carry it.
How to fix it
- 1.
See the chain and the trust store in use
openssl s_client shows what the server sends. javax.net.debug=ssl:handshake:verbose makes Java print the trust store path it loaded and every certificate it received.
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.
Use an application trust store that includes your root
Copy the JDK's cacerts (so public CAs still work), import the company or proxy root with keytool, and point the application at it with javax.net.ssl.trustStore and trustStorePassword (or JAVA_TOOL_OPTIONS for tools you do not start yourself).
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.
Import the root into the JDK's cacerts
keytool -cacerts targets the default store of the JDK that runs keytool (password "changeit" by default). Every application on that JDK trusts the root; the change is lost when the JDK is replaced.
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.
Containers: let the image import your CA
When USE_SYSTEM_CA_CERTS is set, the entrypoint of Eclipse Temurin images adds the *.crt files mounted at /certificates to the JVM's cacerts at start-up (it only runs with the image's default entrypoint). Other images need the keytool step in the 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.
Give one client its own trust store
Build an SSLContext from a TrustManagerFactory initialised with your trust store and pass it to HttpClient (or OkHttp, Apache HttpClient, Netty). Never install a trust manager that accepts every certificate.
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();
Related errors
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