• Java

PKIX path building failed … unable to find valid certification path to requested target

Also seen in: Maven, Gradle, Spring Boot, Jenkins, IntelliJ IDEA, Kafka clients, JDBC drivers, Kotlin, Scala

In one sentence

Java found no path to a root in the JDK's cacerts: usually a company or proxy root was never imported, or the server leaves out its intermediate.

Where you see it

In any Java program that opens an HTTPS or TLS connection: HttpClient, HttpsURLConnection, Apache HttpClient, OkHttp, JDBC drivers, Kafka clients, and tools such as Maven, Gradle and Jenkins. A typical stack trace, shortened:

javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at java.base/sun.security.ssl.Alert.createSSLException(...)
	...
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	...
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Newer Java releases (JDK 23 and later, 21.0.9 and later, 17.0.19 and later) put the TLS alert in front: javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: …. Build tools wrap it in their own message, but the PKIX path building failed part is always there. The texts come from OpenJDK’s PKIXValidator and SunCertPathBuilder.

What it means

PKIX is the name of the X.509 path validation rules in RFC 5280. Java tried to build a certificate chain from the server’s certificate to a trusted root certificate and found none. Java uses its own trust store, the cacerts file inside the JDK (or jssecacerts next to it, if that file exists), not the Windows or macOS store. It also does not download a missing intermediate: that feature is off by default (Java PKI Programmer’s Guide). So a site that works in the browser can fail in Java.

Likely causes

A company or proxy root is not in the JDK’s cacerts

The most common cause at work: internal servers use a company CA, or a TLS-inspection proxy re-signs public sites such as Maven Central. Check who issued the certificate:

openssl s_client -connect repo.example.com:443 -servername repo.example.com -showcerts </dev/null 2>/dev/null | grep -E '^ [0-9]+ s:|^   i:'

Find the JDK that actually runs the failing program, and whether its cacerts holds the root:

java -XshowSettings:properties -version 2>&1 | grep java.home
keytool -list -v -cacerts -storepass changeit | grep -i "Owner:.*Example Corp"

Fix: import the root (PEM or DER) into that JDK’s cacerts. -cacerts works from JDK 9; changeit is the default password on JDK 17 and older and on Debian and Ubuntu, while newer JDK builds (such as Temurin 18 and later) ship cacerts without one (keytool documentation). Use sudo or an administrator terminal for a system-wide JDK:

keytool -importcert -cacerts -alias example-corp-root -file corp-root.pem -storepass changeit -noprompt

Watch out for several JDKs on one machine: the IDE’s bundled runtime, Gradle toolchains, the JAVA_HOME that Maven uses, and the JDK inside a container image are all separate. A JDK update also replaces cacerts.

On Debian and Ubuntu (with ca-certificates-java) and on RHEL and Fedora, the distribution’s own OpenJDK builds cacerts from the system store, so adding the root with update-ca-certificates or update-ca-trust extract covers Java too.

To keep the change out of the JDK, give the application its own trust store. Start from a copy of cacerts, because setting javax.net.ssl.trustStore replaces the default store completely:

cp "$JAVA_HOME/lib/security/cacerts" app-truststore
keytool -importcert -keystore app-truststore -storepass changeit -alias example-corp-root -file corp-root.pem -noprompt
java -Djavax.net.ssl.trustStore=app-truststore -Djavax.net.ssl.trustStorePassword=changeit -jar app.jar

If a jssecacerts file exists in the JDK’s lib/security folder, Java uses it instead of cacerts, so import there or remove the stale file. For Maven, pass the same properties in MAVEN_OPTS; for Gradle, as systemProp.javax.net.ssl.trustStore=… lines in gradle.properties.

The server leaves out its intermediate

Count the certificates the server sends:

openssl s_client -connect repo.example.com:443 -servername repo.example.com -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERTIFICATE'

1 means the intermediate certificate is missing. Java can be told to fetch it through AIA (com.sun.security.enableAIAcaIssuers), but that only hides a server mistake. Fix it on the server: serve the certificate followed by the intermediate (fullchain.pem in nginx, SSLCertificateFile with the chain in Apache, the intermediate store on IIS, the chain field on load balancers). See verify error:num=21.

If the message is PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed, with CertificateExpiredException: NotAfter: … as the cause, the chain is complete but a certificate has expired. Renew it, or check the client’s clock: see certificate has expired.

The JDK is old

An old JDK ships an old cacerts that may lack newer public roots. Update to a current patch release of your Java version.

Check yours

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

Paste the certificates from openssl s_client -showcerts. The checker shows whether a link is missing or the chain ends at a root you still need to import.

Learn why

In the gameLevel 12: Who do we trust?Registry of Roots, Keystone. In development

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9