Skip to content
ecdsa.com
Java (JSSE, SunX509 trust manager)Java

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

How to fix it

  1. 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. 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. 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. 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. 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

Browse the full signature error database