The Situation
It's 2 AM. Your service just started throwing this in production:
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
The endpoint worked fine an hour ago. Nothing changed in your code. But something did change โ the remote server may have rotated its certificate, your JDK got updated, or you just deployed to a new server that's missing a CA cert.
Here's how to diagnose and fix it fast.
What's Actually Happening
Java's SSL engine tries to verify the remote server's certificate chain. It walks up that chain looking for a trusted root CA in its truststore ($JAVA_HOME/lib/security/cacerts). No match found โ it throws PKIX path building failed.
The usual suspects:
- The server uses a certificate signed by a private/internal CA not in Java's default truststore
- The server is using a self-signed certificate
- The server's certificate chain is incomplete (missing intermediate CA)
- Your JDK is outdated and missing newer root CAs โ Let's Encrypt's ISRG Root X1, for example, wasn't in Java's truststore until 8u291
- A corporate proxy is doing SSL inspection, intercepting traffic and re-signing with its own cert
Step 1: Identify Which Certificate Is Missing
Start by checking what the server actually sends:
# Check the server's certificate chain
openssl s_client -connect your-api.example.com:443 -showcerts 2>/dev/null | openssl x509 -noout -issuer -subject
# Or see the full chain
openssl s_client -connect your-api.example.com:443 -showcerts 2>/dev/null
Look for Verify return code: 0 (ok). If OpenSSL verifies fine but Java doesn't, the problem is squarely in Java's truststore โ not the server.
Check whether Java already has the CA:
keytool -list -cacerts -storepass changeit | grep -i "issuer-name"
Step 2: Quick Fix โ Import the Certificate
Download the server's certificate (or the CA cert that signed it):
# Download the server cert
openssl s_client -connect your-api.example.com:443 -showcerts 2>/dev/null < /dev/null \
| openssl x509 -outform PEM > server.crt
# If it's a chain, save all certs from the output manually
# Each -----BEGIN CERTIFICATE----- block is one cert
Import it into Java's truststore:
# Find your JAVA_HOME
java -XshowSettings:all -version 2>&1 | grep java.home
# Import the cert (run as root/admin if needed)
keytool -import -alias my-server-cert \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit \
-file server.crt \
-noprompt
Restart your application. The error should be gone.
Path note: On Java 9+ the truststore lives at $JAVA_HOME/lib/security/cacerts. On Java 8 it's one level deeper: $JAVA_HOME/jre/lib/security/cacerts. Java 9+ can also inherit from the OS truststore depending on configuration.
Step 3: Permanent Fix โ Bundle a Custom Truststore
Importing into the system JDK truststore works, but JDK updates can wipe it. A safer approach: bundle the CA cert with your app and point the JVM at your own truststore.
Create a custom truststore
# Start from a copy of the default cacerts
cp $JAVA_HOME/lib/security/cacerts my-app-truststore.jks
# Add your CA cert to it
keytool -import -alias internal-ca \
-keystore my-app-truststore.jks \
-storepass changeit \
-file internal-ca.crt \
-noprompt
Point your JVM to it at startup
java -Djavax.net.ssl.trustStore=/path/to/my-app-truststore.jks \
-Djavax.net.ssl.trustStorePassword=changeit \
-jar your-app.jar
Or configure it in code (Spring Boot / programmatic)
System.setProperty("javax.net.ssl.trustStore", "/path/to/my-app-truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
Place this before any HTTPS connection is made โ ideally at app startup, before the Spring context loads.
Special Case: Corporate SSL Inspection Proxy
On a corporate network, the proxy may intercept HTTPS traffic and re-sign responses with its own CA. The certificate your app receives is issued by something like "Zscaler" or your company name โ not the actual remote CA.
# Check who issued the cert you're actually seeing
openssl s_client -connect your-api.example.com:443 2>/dev/null | grep "issuer"
See your corporate proxy as the issuer? Ask IT for the proxy's root CA certificate and import it using the steps above.
Special Case: Let's Encrypt on Older JDK
ISRG Root X1 โ Let's Encrypt's root CA โ landed in Java's truststore with 8u291 and 11.0.11. Older JDKs simply don't have it.
# Check your JDK version
java -version
# Check if ISRG Root X1 is present
keytool -list -cacerts -storepass changeit | grep -i "isrg"
Update the JDK โ that's the cleanest fix. If an update isn't on the table right now, download ISRG Root X1 from letsencrypt.org and import it manually.
Verify the Fix
# Use Java's SSL debug output to confirm the handshake succeeds
java -Djavax.net.debug=ssl:handshake -jar your-app.jar 2>&1 | grep -E "(Certificate chain|Finished|Exception)"
A successful handshake shows Finished with no exception. The debug output includes the full handshake log and which certificates were validated.
Quick smoke test without deploying:
curl -v https://your-api.example.com/health
# bypass SSL entirely just to confirm the endpoint is alive
wget --no-check-certificate https://your-api.example.com/health
What NOT to Do
Stack Overflow is full of answers suggesting this:
// DO NOT DO THIS IN PRODUCTION
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
This disables all certificate validation. Every connection becomes vulnerable to MITM attacks โ silently, with zero warnings. Fine for a 10-minute local debug session. Never acceptable in any deployed environment.
Tips
When downloading CA certs from the internet, verify the file before importing it. A corrupted or tampered cert will either silently fail or, worse, import something you didn't intend. ToolCraft's Hash Generator runs entirely in the browser โ paste your cert content, get a SHA-256 hash, compare it against the official source. No upload, no server involved.
Also worth wiring into your CI/CD pipeline: a keytool -list check that confirms your custom truststore contains all expected aliases before a deploy goes out. Beats finding a missing cert at 2 AM.

