How-to guide · Trust a CA

How to add a CA to a Java trust store (cacerts)

In one sentence

Your Java program trusts your private root CA, either through the JDK's cacerts or through its own trust store, and you have proved it with a real connection.

Task
Trust a CA
Platforms
  • Java
  • Linux
  • Windows
  • macOS
  • Docker
Who it is for
Level
Practitioner · 4 steps
On this page
  1. Before you start
  2. Steps
  3. 1. Find the Java that runs your program
  4. 2. Import the root into the JDK’s cacerts
  5. 3. Or give one application its own trust store
  6. 4. Or let Java use the operating system’s store
  7. Check it worked
  8. If something goes wrong
  9. Why this works
  10. Go deeper

Before you start

  • The root certificate of your private CA (PEM or DER). Never a private key.
  • Its SHA-256 fingerprint, from a channel you trust.
  • Which Java runs the failing program. A machine often has several.
  • Write access to that JDK’s folder (sudo or an administrator terminal) if you change its cacerts.

Why it matters: Java ignores the Windows and macOS stores and uses its own trust store: a keystore file named cacerts in JAVA_HOME/lib/security. A root that is trusted everywhere else still gives PKIX path building failed until Java knows it.

Steps

1. Find the Java that runs your program

java -XshowSettings:properties -version 2>&1 | grep -E 'java.home|java.version ='
    java.home = /opt/jdk-25.0.4.1+1
    java.version = 25.0.4.1

Run this with the same java your program uses. IDEs ship their own runtime, Gradle toolchains and Maven’s JAVA_HOME may differ, and every container image has its own JDK. Changing the wrong one is the most common reason “it still fails”.

If a file named jssecacerts exists in that JDK’s lib/security folder, Java uses it instead of cacerts (JSSE).

2. Import the root into the JDK’s cacerts

-cacerts (JDK 9 and later) selects the right file and type for you (keytool). Leave out -noprompt the first time, so keytool shows you the fingerprints before it asks:

sudo keytool -importcert -cacerts -alias example-corp-root -file corp-root.crt
Owner: CN=Example Corp Root CA, O=Example Corp, C=BE
Issuer: CN=Example Corp Root CA, O=Example Corp, C=BE
Serial number: 15d0a60c3f02987ab1646cb51bba6b3d07450314
Valid from: Tue Oct 20 03:43:50 CEST 2026 until: Fri Oct 17 03:43:50 CEST 2036
Certificate fingerprints:
	 SHA1: 3A:54:70:D8:9C:85:77:FE:DC:B8:17:10:DE:F4:23:71:21:4A:F7:08
	 SHA256: BC:02:A4:F5:DC:68:B3:FD:CD:C4:78:03:0E:30:8C:B5:9D:6D:A0:84:5A:A0:B4:33:36:60:78:8E:96:C2:2E:01
Signature algorithm name: SHA256withECDSA
Subject Public Key Algorithm: 384-bit EC (secp384r1) key
...
BasicConstraints:[
  CA:true
  PathLen: no limit
]
...
Trust this certificate? [no]:  yes
Certificate was added to keystore

Compare the SHA256 line with the published fingerprint before you type yes, and check that Owner and Issuer are the same and CA:true is set.

On the Temurin JDK 25 we used, cacerts is a password-less PKCS #12 file, so keytool did not ask for a password. Where cacerts is a JKS file (JDK 17 and earlier, and Debian and Ubuntu’s OpenJDK), it asks: the initial password is changeit, which administrators should change. In scripts, -storepass changeit -noprompt works with both kinds.

3. Or give one application its own trust store

To leave the JDK untouched, start from a copy of cacerts. Setting javax.net.ssl.trustStore replaces the default store completely:

cp "$JAVA_HOME/lib/security/cacerts" app-truststore.p12
keytool -importcert -keystore app-truststore.p12 -storepass changeit -alias example-corp-root -file corp-root.crt -noprompt
java -Djavax.net.ssl.trustStore=app-truststore.p12 -Djavax.net.ssl.trustStorePassword=changeit -jar app.jar

We checked what happens without the copy: a trust store holding only the private root reached the internal server, but a public site then failed with PKIX path building failed.

To pass the same options to every JVM started from a shell or service, put them in JAVA_TOOL_OPTIONS. For Maven use MAVEN_OPTS; for Gradle, systemProp.javax.net.ssl.trustStore=… lines in gradle.properties.

4. Or let Java use the operating system’s store

  • Linux distribution JDKs: on Debian and Ubuntu (with ca-certificates-java) and on the RHEL family, adding the root with update-ca-certificates or update-ca-trust also updates Java’s cacerts. See Linux.
  • Windows: the SunMSCAPI provider exposes the Windows store as a keystore (Oracle providers). -Djavax.net.ssl.trustStore=NONE -Djavax.net.ssl.trustStoreType=Windows-ROOT makes Java trust what Windows trusts; use Windows-ROOT-LOCALMACHINE for services (JDK 19 and later, and OpenJDK 17.0.5 and 11.0.18 and later). See Windows. Windows downloads some public roots only when a Windows program first needs them, and Java does not trigger that download, so test public sites too.
  • Eclipse Temurin containers: mount your PEM .crt files at /certificates and set USE_SYSTEM_CA_CERTS=1; at container start, the image’s entrypoint adds them to the JVM trust store and the system store, unless you replace that entrypoint (image documentation).
FROM eclipse-temurin:25-jre
ENV USE_SYSTEM_CA_CERTS=1
COPY corp-root.crt /certificates/corp-root.crt

Check it worked

List the entry and compare the fingerprint:

keytool -list -cacerts -alias example-corp-root
example-corp-root, Oct 20, 2026, trustedCertEntry, 
Certificate fingerprint (SHA-256): BC:02:A4:F5:DC:68:B3:FD:CD:C4:78:03:0E:30:8C:B5:9D:6D:A0:84:5A:A0:B4:33:36:60:78:8E:96:C2:2E:01

Then make a real connection with the same JDK. Save this as TlsCheck.java:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TlsCheck {
    public static void main(String[] args) throws Exception {
        HttpResponse<Void> r = HttpClient.newHttpClient().send(
            HttpRequest.newBuilder(URI.create(args[0])).build(),
            HttpResponse.BodyHandlers.discarding());
        System.out.println("HTTP " + r.statusCode());
    }
}

Java 11 and later run a single source file directly. Against our test server, before and after the import:

java TlsCheck.java https://localhost:18443/
Exception in thread "main" javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
HTTP 200

Add the -Djavax.net.ssl.trustStore=… options to test an application trust store.

If something goes wrong

  • Still PKIX path building failed. You changed a different JDK (step 1), a jssecacerts file takes priority, or a JDK update replaced cacerts and your import with it. Prefer an application trust store or the operating system route, which survive updates.
  • A typo in javax.net.ssl.trustStore. If the file cannot be read, OpenJDK silently falls back to cacerts (TrustStoreManager), so your store is ignored without an error. Check the path and permissions.
  • keytool error: java.io.FileNotFoundException: …/cacerts (Permission denied). In our test keytool first printed Certificate was added to keystore and then failed to save it. Run it with sudo or as administrator.
  • Keystore was tampered with, or password was incorrect. The cacerts password is not changeit on this machine; ask whoever manages the JDK.
  • The chain is incomplete. Java does not download missing intermediates by default. Fix the server: build the full chain file.
  • The certificate does not match the address, for example No subject alternative names matching IP address, or it has expired. Those are problems with the server certificate, not with trust.

Why this works

During the handshake, Java’s default trust manager builds a certificate chain from the server’s certificate to a trusted certificate in the trust store it loaded at startup: javax.net.ssl.trustStore if set, otherwise jssecacerts, otherwise cacerts. With your root in that store, path validation finds an anchor and succeeds. Why each runtime has its own store is in Who do we trust?, and the validation rules are in Certificate chains and path validation.

Go deeper

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9