<!-- https://pkizerotohero.com/guides/add-root-ca-java/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Import a private root CA into Java's cacerts with keytool, or give one app its own trust store. Real JDK 25 output, checks, and the traps to avoid.

Source: https://pkizerotohero.com/guides/add-root-ca-java/ · Last reviewed: 2026-10-20

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](https://pkizerotohero.com/guides/#task-trust)

Platforms

- Java
- Linux
- Windows
- macOS
- Docker

Who it is for

- [Developer](https://pkizerotohero.com/start/#developer)
- [IT ops](https://pkizerotohero.com/start/#itops)

Level

Practitioner · 4 steps

## Before you start

- The [root certificate](https://pkizerotohero.com/handbook/glossary/#root-certificate) of your [private CA](https://pkizerotohero.com/handbook/glossary/#private-ca) (PEM or DER). Never a private key.
- Its SHA-256 [fingerprint](https://pkizerotohero.com/handbook/glossary/#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](https://pkizerotohero.com/handbook/glossary/#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`](https://pkizerotohero.com/errors/java-pkix-path-building-failed/) until Java knows it.

## Steps

### 1. Find the Java that runs your program

```bash
java -XshowSettings:properties -version 2>&1 | grep -E 'java.home|java.version ='
```

```text
    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](https://docs.oracle.com/en/java/javase/25/security/java-secure-socket-extension-jsse-reference-guide.html)).

### 2. Import the root into the JDK’s cacerts

`-cacerts` (JDK 9 and later) selects the right file and type for you ([keytool](https://docs.oracle.com/en/java/javase/25/docs/specs/man/keytool.html)). Leave out `-noprompt` the first time, so keytool shows you the fingerprints before it asks:

```bash
sudo keytool -importcert -cacerts -alias example-corp-root -file corp-root.crt
```

```text
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:

```bash
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](https://pkizerotohero.com/guides/add-root-ca-linux/).
- **Windows:** the SunMSCAPI provider exposes the Windows store as a keystore ([Oracle providers](https://docs.oracle.com/en/java/javase/25/security/oracle-providers.html)). `-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](https://pkizerotohero.com/guides/add-root-ca-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](https://hub.docker.com/_/eclipse-temurin)).

```dockerfile
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:

```bash
keytool -list -cacerts -alias example-corp-root
```

```text
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`:

```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:

```bash
java TlsCheck.java https://localhost:18443/
```

```text
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
```

```text
HTTP 200
```

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

## If something goes wrong

- **Still [`PKIX path building failed`](https://pkizerotohero.com/errors/java-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`](https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java)), 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](https://pkizerotohero.com/guides/build-fullchain/).
- **The certificate does not match the address,** for example [`No subject alternative names matching IP address`](https://pkizerotohero.com/errors/java-no-subject-alternative-names/), or [it has expired](https://pkizerotohero.com/errors/java-certificate-expired/). Those are problems with the server certificate, not with trust.

Warning

Never install a trust-all TrustManager

A `TrustManager` that accepts every certificate, or a build-tool option that skips validation, makes the error disappear by trusting anyone, including whoever intercepts your dependency downloads. Import the one root you need. To remove it later: `keytool -delete -cacerts -alias example-corp-root`.

## Why this works

During the handshake, Java’s default trust manager builds a [certificate chain](https://pkizerotohero.com/handbook/glossary/#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](https://pkizerotohero.com/handbook/glossary/#path-validation) finds an anchor and succeeds. Why each runtime has its own store is in [Who do we trust?](https://pkizerotohero.com/handbook/trust-stores/), and the validation rules are in [Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/).

## Go deeper

- [keytool](https://docs.oracle.com/en/java/javase/25/docs/specs/man/keytool.html): `-importcert`, `-cacerts`, `-list`, `-delete` and the `cacerts` file.
- [JSSE Reference Guide](https://docs.oracle.com/en/java/javase/25/security/java-secure-socket-extension-jsse-reference-guide.html): the trust store system properties and their defaults.
- [JDK providers](https://docs.oracle.com/en/java/javase/25/security/oracle-providers.html): the Windows keystore types.
- [Eclipse Temurin image](https://hub.docker.com/_/eclipse-temurin): adding CA certificates in containers.

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/guides/add-root-ca-java/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

### Learn why it works

- [Chapter 12: Who do we trust? CAs, trust stores and self-signed certificates](https://pkizerotohero.com/handbook/trust-stores/)
- [Chapter 13: Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)
- [Chapter 22: A CA of your own: private two-tier PKI](https://pkizerotohero.com/handbook/private-ca/)

### Error pages

- [PKIX path building failed … unable to find valid certification path to requested target](https://pkizerotohero.com/errors/java-pkix-path-building-failed/)
- [No subject alternative names matching IP address … found](https://pkizerotohero.com/errors/java-no-subject-alternative-names/)
- [CertPathValidatorException: validity check failed](https://pkizerotohero.com/errors/java-certificate-expired/)

### Tools

- [Certificate decoder](https://pkizerotohero.com/tools/decoder/): Paste a PEM certificate and read every field in plain words.
- [Chain checker](https://pkizerotohero.com/tools/chain/): Paste a chain and see which link is missing, expired or out of order.

### Topics

- [Chains and trust stores](https://pkizerotohero.com/topics/chains-and-trust/)
- [Private PKI](https://pkizerotohero.com/topics/private-pki/)

## More guides: Trust a CA

- [How to work behind a TLS-inspecting corporate proxy, safely](https://pkizerotohero.com/guides/corporate-proxy-certificates/)
- [How to trust a private root CA on Windows](https://pkizerotohero.com/guides/add-root-ca-windows/)
- [How to trust a private root CA on macOS](https://pkizerotohero.com/guides/add-root-ca-macos/)
- [How to trust a private root CA on Linux](https://pkizerotohero.com/guides/add-root-ca-linux/)
- [How to make Python, Node.js and npm trust a private CA](https://pkizerotohero.com/guides/trust-ca-python-node/)
