How to solve the 'PKIX path building failed' error in Java?
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:keytool -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.
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.