How to solve the 'PKIX path building failed' error in Java?

Abdul D.
—The Problem
I encounter the PKIX path building failed error when trying to connect to a server using HTTPS:
import java.io.IOException; import java.net.URL; import javax.net.ssl.HttpsURLConnection; public class Main { public static void main(String[] args) { try { URL url = new URL("https://expired-rsa-ev.ssl.com/"); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); System.out.println("Response Code: " + connection.getResponseCode()); } catch (IOException e) { e.printStackTrace(); } } }
Trying to connect to the URL in the code above throws the following exception:
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 at sun.security.ssl.Alert.createSSLException(Alert.java:131) at sun.security.ssl.TransportContext.fatal(TransportContext.java:324) at sun.security.ssl.TransportContext.fatal(TransportContext.java:267) at sun.security.ssl.TransportContext.fatal(TransportContext.java:262) at sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1325) at sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1200) at sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1143) at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392) at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444) at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:422) at sun.security.ssl.TransportContext.dispatch(TransportContext.java:182) at sun.security.ssl.SSLTransport.decode(SSLTransport.java:152) at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1198) at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1107) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:398) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:370) at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:567) at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185) at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:162) at Main.main(Main.java:9)
The Solution
This error occurs when Java cannot verify the SSL certificate of the server you are trying to connect to. This is usually because the certificate isn’t trusted by the Java TrustStore. You can resolve this issue by importing the SSL certificate into the default Java TrustStore or your own custom TrustStore. Alternatively, you can disable SSL certificate validation. This last option is not recommended, as it compromises security.
Importing the Server Certificate into Java TrustStore
The most common solution is to import the server’s SSL certificate into your Java TrustStore.
Open your browser and export the server’s certificate in
.cerformat.Use the
keytoolcommand to import the certificate into the Java TrustStore:Click to Copykeytool -import -alias server_cert -file server.cer -keystore cacerts- The default location of the
cacertsTrustStore is usuallyJAVA_HOME/lib/security/cacerts. - The default password for the TrustStore is
changeit.
- The default location of the
Specifying a Custom TrustStore
If you don’t want to modify the default TrustStore, you can create a custom TrustStore for your application.
First, create a TrustStore and import the certificate:
keytool -import -alias server_cert -file server.cer -keystore customTrustStore.jks
Then specify the custom TrustStore when running your Java application:
java -Djavax.net.ssl.trustStore=customTrustStore.jks -Djavax.net.ssl.trustStorePassword=your_password YourApplication
Disabling Certificate Validation
For development purposes, you can disable SSL certificate validation. However, this is not recommended for production environments as it compromises security.
The following example demonstrates how to disable certificate validation by creating a method to trust all certificates:
/** * Configures SSL to trust all certificates and bypass hostname verification. * This allows the application to connect to servers with untrusted or self-signed certificates. * WARNING: This approach is insecure for production use. */ private static void configureTrustAllSSL() throws Exception { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { // Do nothing - trust all clients } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { // Do nothing - trust all servers } } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); }
You can call this method before making the HTTPS connection. This will bypass SSL certificate validation and allow you to connect to servers with untrusted or self-signed certificates.
public static void main(String[] args) { try { // Configure SSL to trust all certificates configureTrustAllSSL(); // URL of the page with an expired or untrusted certificate URL url = new URL("https://expired-rsa-ev.ssl.com/"); // Replace with your URL String pageContent = fetchContentFromURL(url); // Print out the content of the page System.out.println(pageContent); } catch (Exception e) { e.printStackTrace(); } }
The output will display html page content representing a successful connection:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <link rel="icon" href="favicon-32x32.png" sizes="32x32" /> <link rel="icon" href="favicon-192x192.png" sizes="192x192" /> <link rel="apple-touch-icon" href="favicon-180x180.png" /> <title>SSL.com - Test Website</title> </head> <body> <h1>SSL.com - Test Website</h1> <p>This is a test website authenticated by <a href="https://www.ssl.com" target="_blank">SSL.com.</a></p> </body> </html>
Here’s the full example code:
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.security.cert.X509Certificate; import javax.net.ssl.*; public class SSLBypassExample { public static void main(String[] args) { try { // Configure SSL to trust all certificates configureTrustAllSSL(); // URL of the page with an expired or untrusted certificate URL url = new URL("https://expired-rsa-ev.ssl.com/"); // Replace with your URL String pageContent = fetchContentFromURL(url); // Print the content of the page System.out.println(pageContent); } catch (Exception e) { e.printStackTrace(); } } /** * Configures SSL to trust all certificates and bypass hostname verification. * This allows the application to connect to servers with untrusted or self-signed certificates. * WARNING: This approach is insecure for production use. */ private static void configureTrustAllSSL() throws Exception { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { // Do nothing - trust all clients } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { // Do nothing - trust all servers } } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); } /** * Fetches the content from the specified URL as a String. * * @param url The URL to fetch content from. * @return The content of the page as a String. * @throws Exception If an error occurs while reading from the URL. */ private static String fetchContentFromURL(URL url) throws Exception { StringBuilder content = new StringBuilder(); // Open HTTPS connection and read content HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { String inputLine; while ((inputLine = in.readLine()) != null) { content.append(inputLine); } } return content.toString(); } }
Warning: This should only be used in development or testing environments where security is not a concern.
- Sentry BlogException Handling in Java (with Real Examples) (opens in a new tab)
- Syntax.fmListen to the Syntax Podcast (opens in a new tab)
- Listen to the Syntax Podcast (opens in a new tab)
![Syntax.fm logo]()
Tasty treats for web developers brought to you by Sentry. Get tips and tricks from Wes Bos and Scott Tolinski.
SEE EPISODES
Considered “not bad” by 4 million developers and more than 150,000 organizations worldwide, Sentry provides code-level observability to many of the world’s best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.
