How to Configure Java javax.net.ssl.trustStore and keyStore Properties

Set javax.net.ssl.trustStore and keyStore JVM properties after building a PKCS12 file with keytool: property names, -D flags, JAVA_TOOL_OPTIONS, and a lab that fails without trustStore then succeeds.

Published

Updated

Read time 9 min read

Reviewed byDeepak Prasad

Configure Java trustStore and keyStore JVM properties banner with keytool PKCS12 file and -D flags

You built trust.p12 with keytool -importcert, but the application still throws PKIX path building failed. The missing step is telling the JVM which file to load at runtime. Java reads TLS material from system properties whose names start with javax.net.ssl. — not from the filename alone, and not from environment variables unless your startup script maps them to -D flags.

This guide lists every trustStore and keyStore property, builds a PKCS12 truststore with keytool, runs a client with and without -Djavax.net.ssl.trustStore, and shows how to confirm what a live process actually picked up. For the conceptual split between identity and trust files, see Java keystore vs truststore. For when to avoid editing global cacerts, see Custom truststore vs cacerts.

Tested on: Ubuntu 26.04 LTS; OpenJDK 25.0.3; kernel 7.0.0-27-generic.


Prerequisites


JVM property reference

Oracle's JSSE reference guide documents these system properties. IBM and other vendors use the same names for plain Java TLS:

Property Purpose Typical value
javax.net.ssl.trustStore Path to truststore file /opt/app/trust.p12
javax.net.ssl.trustStorePassword Truststore password changeit (or your secret)
javax.net.ssl.trustStoreType Keystore format PKCS12 or JKS
javax.net.ssl.keyStore Path to identity keystore /opt/app/server.p12
javax.net.ssl.keyStorePassword Identity keystore password your store password
javax.net.ssl.keyStoreType Identity keystore format PKCS12

Trust properties control who the JVM trusts on outbound HTTPS, JDBC over TLS, LDAPS, and similar client connections. Key store properties supply the default identity keystore some APIs read when you do not configure SSL in application code.

NOTE
Property names are case-sensitive and include the full javax.net.ssl. prefix. Forum posts sometimes shorten them to ssl.trustStore — that is not a valid JSSE property and is silently ignored.

Default behavior when properties are unset

JSSE resolves the default truststore in this order:

  1. File set with -Djavax.net.ssl.trustStore
  2. java.home/lib/security/jssecacerts if it exists
  3. java.home/lib/security/cacerts

When javax.net.ssl.trustStore is null, JSSE skips step 1 and falls back to jssecacerts, then cacerts.

When javax.net.ssl.keyStore is null, there is no global default identity keystore — embedded servers and frameworks set identity through their own config (server.xml, Spring Boot server.ssl.*, etc.).

Print what the JVM sees before you add flags:

bash
cat > PropsProbe.java << 'EOF'
public class PropsProbe {
  public static void main(String[] args) {
    String[] keys = {
      "javax.net.ssl.trustStore", "javax.net.ssl.trustStorePassword", "javax.net.ssl.trustStoreType",
      "javax.net.ssl.keyStore", "javax.net.ssl.keyStorePassword", "javax.net.ssl.keyStoreType",
      "java.home"
    };
    for (String k : keys) System.out.println(k + "=" + System.getProperty(k));
  }
}
EOF
javac PropsProbe.java
java PropsProbe
text
javax.net.ssl.trustStore=null
javax.net.ssl.trustStorePassword=null
javax.net.ssl.trustStoreType=null
javax.net.ssl.keyStore=null
javax.net.ssl.keyStorePassword=null
javax.net.ssl.keyStoreType=null
java.home=/usr/lib/jvm/java-25-openjdk-amd64

All null means the process uses JSSE default truststore lookup: jssecacerts if present, otherwise cacerts. It also has no global identity keystore from system properties.


Build a truststore with keytool

Create a PKCS12 file with your private CA (or copy from your PKI team). The snippet below uses a quick lab CA; for a dedicated root signer see create a root CA on Linux:

bash
mkdir -p ~/ssl-props-lab/certs && cd ~/ssl-props-lab

openssl req -x509 -newkey rsa:2048 -keyout certs/lab-ca.key -out certs/lab-ca.crt \
  -days 365 -nodes -subj "/CN=Props Lab CA/O=Lab/C=US" 2>/dev/null

keytool -importcert -alias props-lab-ca -file certs/lab-ca.crt \
  -keystore trust.p12 -storetype PKCS12 -storepass changeit -noprompt
keytool -list -keystore trust.p12 -storepass changeit | head -8
text
Certificate was added to keystore
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

props-lab-ca, Jul 3, 2026, trustedCertEntry,
Certificate fingerprint (SHA-256): E1:DA:CB:10:D0:9E:EF:CD:E8:7E:E3:DE:C4:0F:20:EE:74:BD:36:C1:B7:F1:44:4E:49:4B:62:C1:19:E2:B6:73

Only trustedCertEntry rows belong in a truststore. Do not import your server's private key here — that belongs in a keystore loaded with javax.net.ssl.keyStore.


Lab: client without and with trustStore

Start a local HTTPS server signed by the lab CA (SAN includes 127.0.0.1):

bash
openssl genrsa -out certs/server.key 2048 2>/dev/null
openssl req -new -key certs/server.key -out certs/server.csr \
  -subj "/CN=internal.lab.local/O=Lab/C=US" 2>/dev/null
cat > certs/server.ext << 'EOF'
subjectAltName = IP:127.0.0.1,DNS:internal.lab.local
extendedKeyUsage = serverAuth
EOF
openssl x509 -req -in certs/server.csr -CA certs/lab-ca.crt -CAkey certs/lab-ca.key \
  -CAcreateserial -out certs/server.crt -days 365 -extfile certs/server.ext 2>/dev/null

openssl s_server -accept 8443 -cert certs/server.crt -key certs/server.key -www \
  </dev/null >/dev/null 2>&1 &
SERVER_PID=$!
sleep 1

TLS client without custom truststore — uses JSSE default truststore lookup:

bash
cat > TlsClient.java << 'EOF'
import javax.net.ssl.HttpsURLConnection;
import java.net.URL;
public class TlsClient {
  public static void main(String[] args) throws Exception {
    try {
      HttpsURLConnection c = (HttpsURLConnection) new URL("https://127.0.0.1:8443/").openConnection();
      c.setConnectTimeout(5000);
      c.setReadTimeout(5000);
      c.connect();
      System.out.println("HTTP status: " + c.getResponseCode());
    } catch (Exception e) {
      String msg = e.getMessage();
      if (msg != null && msg.contains("\n")) msg = msg.split("\n")[0];
      System.out.println("FAILED: " + e.getClass().getSimpleName() + ": " + msg);
    }
  }
}
EOF
javac TlsClient.java
java TlsClient
text
FAILED: SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

The lab CA is not in the default JSSE truststore (jssecacerts or cacerts under java.home). Point JSSE at your PKCS12 file by passing all three trust properties on the same java command line, before the main class name:

Property Value in this lab
javax.net.ssl.trustStore Path to trust.p12 ($PWD resolves to the lab directory)
javax.net.ssl.trustStorePassword changeit — same password used during keytool -importcert
javax.net.ssl.trustStoreType PKCS12 — required for .p12 files on older JDKs and mixed fleets
bash
java \
  -Djavax.net.ssl.trustStore="$PWD/trust.p12" \
  -Djavax.net.ssl.trustStorePassword=changeit \
  -Djavax.net.ssl.trustStoreType=PKCS12 \
  TlsClient

When the flags reach the JVM that runs TlsClient, JSSE loads props-lab-ca from trust.p12, validates the server chain, and the HTTPS call completes:

text
HTTP status: 200

That single line is enough to confirm PKIX trust succeeded — the TLS handshake finished and the server returned a normal HTTP response.

Before you copy the same flags into systemd or Docker, run PropsProbe with the identical -D arguments. It prints what System.getProperty() returns at startup — the same values jcmd PID VM.system_properties reports on a live process:

bash
java \
  -Djavax.net.ssl.trustStore="$PWD/trust.p12" \
  -Djavax.net.ssl.trustStorePassword=changeit \
  -Djavax.net.ssl.trustStoreType=PKCS12 \
  PropsProbe
text
javax.net.ssl.trustStore=/root/ssl-props-lab/trust.p12
javax.net.ssl.trustStorePassword=changeit
javax.net.ssl.trustStoreType=PKCS12
javax.net.ssl.keyStore=null
javax.net.ssl.keyStorePassword=null
javax.net.ssl.keyStoreType=null
java.home=/usr/lib/jvm/java-25-openjdk-amd64

All three trust properties point at your lab file. The keyStore* lines stay null because this client only validates the server — it does not present a client certificate. java.home shows which JDK directory JSSE uses for default trust when trustStore is unset.

trustStore replaces the default JSSE truststore lookup for this process — it does not merge with cacerts.

Clean up:

bash
kill $SERVER_PID 2>/dev/null
rm -rf ~/ssl-props-lab

Pass properties at runtime

NOTE
Avoid hardcoding real truststore or keystore passwords directly in scripts, command history, Dockerfiles, or shared unit files. JVM -D arguments may be visible through process inspection tools such as ps command, /proc/<pid>/cmdline, or jcmd. For production, use a protected systemd environment file, Kubernetes Secret, container runtime secret, or application-specific secret mechanism.

Command line

bash
java \
  -Djavax.net.ssl.trustStore=/opt/app/trust.p12 \
  -Djavax.net.ssl.trustStorePassword=changeit \
  -Djavax.net.ssl.trustStoreType=PKCS12 \
  -jar myapp.jar

Use absolute paths. Relative paths depend on the process working directory, which systemd, Docker, and cron jobs often set differently than your SSH session.

JAVA_TOOL_OPTIONS

Most JVM processes launched through the standard Java launcher pick this up automatically, which makes it useful in containers and wrapper scripts. In locked-down environments, verify it with jcmd or PropsProbe because some launches may ignore or restrict it.

bash
export JAVA_TOOL_OPTIONS="-Djavax.net.ssl.trustStore=/opt/app/trust.p12 -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStoreType=PKCS12"
java -jar myapp.jar

Patterns for Docker and Kubernetes are in Java truststore in Docker and Kubernetes.

Tomcat and systemd

Tomcat setenv.sh or CATALINA_OPTS often carry both trust and identity flags:

bash
JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStore=/opt/tomcat/trust.p12"
JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStorePassword=changeit"
JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStoreType=PKCS12"

For a generic systemd service, use a drop-in. See systemctl command for daemon-reload and unit edit workflows if you rarely touch systemd units.

bash
sudo systemctl edit myapp.service
ini
[Service]
Environment="JAVA_TOOL_OPTIONS=-Djavax.net.ssl.trustStore=/opt/app/trust.p12 -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStoreType=PKCS12"

Then reload and restart:

bash
sudo systemctl daemon-reload
sudo systemctl restart myapp.service

For a running service, confirm what loaded — replace $PID with the Java process ID from systemctl status or pgrep -f myapp:

bash
jcmd "$PID" VM.command_line
jcmd "$PID" VM.system_properties | grep -E 'javax.net.ssl|java.home'
text
javax.net.ssl.trustStore=/opt/app/trust.p12
javax.net.ssl.trustStorePassword=changeit
javax.net.ssl.trustStoreType=PKCS12
java.home=/usr/lib/jvm/java-25-openjdk-amd64

When javax.net.ssl.trustStore lines are absent, the JVM falls back to jssecacerts then cacerts under java.home. See Find the correct Java cacerts file for that path.


keyStore properties for server identity

javax.net.ssl.keyStore* configures the JVM default identity keystore — the file with your PrivateKeyEntry that proves who the TLS endpoint is.

bash
java \
  -Djavax.net.ssl.keyStore=/opt/app/server.p12 \
  -Djavax.net.ssl.keyStorePassword=changeit \
  -Djavax.net.ssl.keyStoreType=PKCS12 \
  -jar myapp.jar

Most application servers do not rely on these globals alone:

Runtime Identity configuration
Tomcat server.xml certificateKeystoreFile / PKCS12 <Certificate> attributes — see Tomcat SSL with keytool
Spring Boot server.ssl.key-store in application.properties — see Spring Boot HTTPS with keytool
Plain HttpsServer / custom code KeyManagerFactory initialized from your PKCS12 file

Do not point keyStore and trustStore at the same file unless you deliberately merged roles — mixing server private keys into a truststore widens blast radius if the file leaks.


Common mistakes

Mistake What happens
-Dssl.trustStore=... (missing javax.net.ssl.) Property ignored; JSSE default truststore lookup is still used
trustStore set without trustStoreType in mixed JKS/PKCS12 environments Usually works on modern JDKs, but can fail on older JDKs or vendor runtimes
Private-CA-only trust.p12 without public CAs Internal HTTPS works; Maven Central / public APIs fail
Flags in your shell, not in the service unit App still uses default trust after restart
Editing cacerts while app uses -Djavax.net.ssl.trustStore Import never reaches the running JVM

Wrong property name demo — ssl.trustStore is not a valid JSSE property, so Java ignores it and PKIX still fails the same way as the first TlsClient run:

bash
java -Dssl.trustStore="$PWD/trust.p12" TlsClient
text
FAILED: SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Troubleshooting

Symptom Likely cause Fix
PKIX after creating trust.p12 Properties not passed to the JVM that runs the app jcmd PID VM.system_properties; fix systemd/Docker env
jcmd shows trustStore, but app still fails Framework or library builds its own SSLContext and ignores JVM defaults Configure truststore in the app or library settings — Spring Boot SSL bundles, Apache HttpClient, Netty, Kafka, Elasticsearch client, or JDBC driver SSL options
Keystore was tampered with on startup Wrong trustStorePassword Match password used at keytool -importcert time
Property set but debug shows cacerts Typo in name or child process without inherited flags VM.command_line; use JAVA_TOOL_OPTIONS
Server starts but wrong cert presented keyStore path wrong; trust flags unrelated Fix keyStore* or Tomcat/Spring identity config

Trigger SSL initialization and read the loaded file:

bash
java -Djavax.net.debug=trustmanager -Djavax.net.ssl.trustStore=/opt/app/trust.p12 \
     -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStoreType=PKCS12 \
     -cp . TrustProbe

Use the TrustProbe helper from Find the correct Java cacerts file — it calls SSLContext.getDefault() so trustStore is: appears in debug output.


References


Summary

keytool creates the file; -Djavax.net.ssl.trustStore* tells the JVM to load it. Set trustStore, trustStorePassword, and trustStoreType for outbound trust; set keyStore* only when you need a global identity keystore. Unset trustStore skips step 1 and falls back to jssecacerts then cacerts. Verify with PropsProbe, jcmd VM.system_properties, or trustmanager debug — and pass the same flags to the process that actually runs your application, not only your interactive shell.


Frequently Asked Questions

1. What is the difference between javax.net.ssl.trustStore and keyStore?

trustStore holds CA certificates you trust when validating remote TLS peers (trustedCertEntry). keyStore holds your identity — private key plus certificate chain (PrivateKeyEntry) for HTTPS servers or mTLS clients. They are separate files and separate -D properties.

2. Do I need all six javax.net.ssl properties?

No. Set trustStore, trustStorePassword, and trustStoreType when configuring outbound trust. Set keyStore, keyStorePassword, and keyStoreType when the JVM needs a default identity keystore. Unset trustStore falls back to JSSE default lookup: jssecacerts if present, otherwise cacerts.

3. Why does my custom truststore property not work?

Common causes: typo in the property name (must be javax.net.ssl.trustStore, not ssl.trustStore), wrong file path, missing or wrong trustStoreType in older JDKs or mixed JKS/PKCS12 environments, wrong password, or flags set only in your shell but not in the service unit that starts the app.

4. Does javax.net.ssl.trustStore merge with cacerts?

No. When trustStore is set, the JVM loads that file instead of the default JSSE truststore lookup — it does not merge with jssecacerts or cacerts. Copy public CAs into your custom file or build a composite truststore if you need both private and public trust.

5. How do I set trustStore for a systemd Java service?

Add -Djavax.net.ssl.trustStore, -Djavax.net.ssl.trustStorePassword, and -Djavax.net.ssl.trustStoreType to JAVA_OPTS or JAVA_TOOL_OPTIONS in the unit file or environment file, then daemon-reload and restart. Confirm with jcmd PID VM.system_properties.

6. Is javax.net.ssl.trustStorePassword required?

Not always. JSSE can load some truststores without javax.net.ssl.trustStorePassword, but then the truststore integrity check may not be protected. For keytool-created PKCS12 files, pass the same password you used during import so failures are explicit and repeatable.

7. Can I set javax.net.ssl.trustStore after the application starts?

It is unreliable. Some TLS libraries initialize the default SSLContext early and may not reload properties changed later with System.setProperty(). Set javax.net.ssl properties before the JVM starts, or configure a custom SSLContext in application code.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …