Short answer
The browser and your runtimes use different trust stores. The browser typically trusts the operating system's store (or its own) and is forgiving about missing intermediates. Java uses its own cacerts, and .NET uses the Windows certificate store (or, on Linux, OpenSSL's store). If the server is missing an intermediate, or the required root is not in the runtime's store, only the runtime fails — not the browser.
The two possible causes
- Missing intermediate on the server. Browsers cache intermediates and can fetch them via AIA; Java and .NET often do not. The result is
unable to get local issuer certificateorPKIX path building failedin Java, while the browser is happy. This is the most common cause — and it is a server fault, not a client fault. - Missing root in the runtime's trust store. A new or internal CA's root is not in Java's
cacertsor the Windows store. Here the root must be imported into the right store.
Work out which one it is
First check whether the server sends the full chain (independent of client):
openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null \ | grep -c "BEGIN CERTIFICATE"
If you get 1, the intermediate is missing on the server — fix it there and Java, .NET and curl all work. If you get 2+, the chain is fine and the problem is the trust store in your runtime.
Java: cacerts and PKIX
The classic Java error is sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path. List what is already in your truststore, and import a missing root:
# List roots in the active JVM keytool -list -keystore "$JAVA_HOME/lib/security/cacerts" -storepass changeit # Import an internal/missing root keytool -importcert -alias corp-root -file corporate-root.crt \ -keystore "$JAVA_HOME/lib/security/cacerts" -storepass changeit
Note the difference between a keystore (your own identity/private key) and a truststore (the CAs you trust). It is the truststore that matters here.
.NET
On Windows, .NET uses the machine certificate store — import the root under "Trusted Root Certification Authorities" (or for the current user). On Linux, .NET uses OpenSSL's CA bundle, so update-ca-certificates after placing the root in /usr/local/share/ca-certificates/ resolves it. Also check that private-key permissions are correct if it is a client certificate.
The key takeaway
Because the browser hides missing intermediates, the fault often slips past QA and only hits a server-to-server integration. CertControl tests the full chain from the outside — like a strict client, not a forgiving browser — and flags servers that send only a partial chain. So you catch the problem before your Java or .NET integrations do. It is one of the common TLS handshake failures, and understanding the certificate chain is what makes the fix obvious.
Frequently asked questions
Why is the browser more forgiving than Java?
Browsers cache intermediates from earlier visits and can fetch missing ones via the certificate's AIA field. Java and .NET typically do neither and require the server to send the full chain itself.
What is the difference between a keystore and a truststore?
A keystore holds your own identity — private key and certificate. A truststore holds the CA root certificates you trust. On a validation error it is almost always the truststore that is missing something.
Should I edit the system cacerts?
Preferably not directly. Use a separate truststore via -Djavax.net.ssl.trustStore, or import into a copy. Modifying the global cacerts can be overwritten at the next JDK update.
PKIX path building failed — what does it mean?
It is Java's version of "could not build a valid chain to a trusted root". Almost always a missing intermediate on the server or a missing root in your truststore.