Skip to content
ssldays

Guide

Java SSLHandshakeException certificate expired

javax.net.ssl SSLHandshakeException certificate expired means the JVM rejected notAfter on your HTTPS or LDAPS peer. Confirm with openssl s_client, renew the server cert, and update truststores if needed.

Updated May 2, 2026 821 words

javax.net.ssl SSLHandshakeException certificate expired is how the JVM reports PKIX failure when notAfter on the server chain is in the past—common in Spring RestTemplate, Apache HttpClient, JDBC over HTTPS, and LDAPS before application code runs.

Java services stay up for weeks; TLS handshakes happen on new connections. An expired API cert breaks nightly batch jobs while interactive sessions on pooled connections might survive until idle timeout—confusing triage timelines.

The JVM uses the default cacerts bundle plus any custom truststore you configure through system properties or container entrypoints. Operations runbooks should list which machines still rely on an outdated cacerts file baked into golden images versus containers that mount truststores dynamically.

Vendor management should know which partner endpoints your JVMs call before asking partners to rotate, because sometimes the expired certificate is yours on the reverse proxy in front of Java, not the partner origin at all. Expiry is independent of trust unless an expired cert is also untrusted; the message still names expiration when dates fail first.

First response

Capture JDK version, vendor (Temurin, Corretto), full stack trace to sun.security.validator.ValidatorException, and target URL. Enable -Djavax.net.debug=ssl,handshake briefly in a controlled repro—not permanently in production.

Note custom truststore system properties (javax.net.ssl.trustStore) and whether FIPS providers change validation order. Record corporate forward proxy settings on HttpClient.

For mutual TLS, inspect javax.net.ssl.keyStore client cert dates—local expiry mimics server errors in some frameworks.

Identify terminator: Tomcat connector, Spring Boot embedded Netty, API gateway, or partner endpoint.

If the stack uses IBM or Oracle commercial JDKs, note whether unrestricted policy jars affect cipher suites—not expiry directly, but renewal windows often coincide with security bundle updates that leave one cluster member on old keystore passwords.

Capture Maven or Gradle profiles active in production because keystore paths differ per profile. A renewed cert uploaded to production configuration does nothing when the running pod still uses a Kubernetes secret mounted from last month without a restart.

War room notes should separate JVM truststore edits from remote server renewal tasks. Teams that only import new intermediates locally while partner notAfter remains in the past extend incidents unnecessarily. Align external openssl proof with Java stack traces before approving long maintenance windows on application tiers.

What this error means

JSSE performs PKIX validation during SSL handshake. The server sends certificates; TrustManager implementations check signature chain to trust anchors, then each cert’s validity period, then hostname via EndpointIdentificationAlgorithm when HTTPS.

SSLHandshakeException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed: NotAfter is the smoking gun for expiry. Intermediate expiry fails even if leaf dates look valid on quick glance.

Unlike browsers, Java does not show friendly padlock UI—logs carry the burden. Fix server notAfter first; updating JVM truststore alone cannot extend remote certificate lifetime.

Peers in Go or Python hitting the same URL should fail similarly—see golang certificate has expired for parallel repro.

SSLHandshakeException is a catch-all header; drill into getCause() for CertificateExpiredException with explicit NotAfter timestamps to paste into infra tickets. Hostname check failures throw CertificateException subclasses with different wording—do not merge runbooks.

Connection pools in Apache HttpClient 5 and Jetty reuse sessions; after server renewal, evictIdleConnections or restart may be required before the next business transaction uses fresh TLS. This is operational validation, not a substitute for fixing server dates.

FIPS mode may reject algorithms on renewed certs signed with deprecated hashes—rare on public CAs today but common when internal CAs lag behind security baselines. Compare openssl signature algorithm with JVM provider release notes if dates look valid yet handshake still fails.

Common causes

Enterprise Java estates accumulate decades of integration URLs in property files, and certificate expiry surfaces them all at once when a shared partner endpoint lapses. Architecture review during the incident should map which business capabilities share the failed hostname so prioritization reflects revenue impact, not only which log volume is loudest. Operations teams supporting both Linux containers and legacy application servers must verify both keystores and reverse proxy PEM paths because renewal playbooks written for one stack rarely mention the other.

Ops renewed Apache SSLCertificateFile but Tomcat behind AJP still serves old keystore JKS. Spring Boot server.ssl.key-store production profile not updated during PKCS12 rotation script.

Partner SaaS API cert expired—internal ticket blames Java until openssl s_client proves external listener. JVM truststore holds obsolete intermediate removed from public web—shows as untrusted more than expired, but dual problems occur after rushed renewals.

WebLogic/WebSphere admin console upload succeeded but managed servers not restarted. Kubernetes ingress secret rotated; Java pod still caches trust from init container mount until roll.

Load-balanced APIs: one node expired, JDBC connection pool sticks to bad member intermittently.

Android apps using network security config are out of scope, but hybrid teams often share the same Spring Boot API with mobile Flutter clients—prove server notAfter once, then let client teams remove temporary trust overrides in parallel once live TLS is clean.

Confirm from the outside

OpenSSL

HOST=api.example.com
echo | openssl s_client -connect "$HOST:443" -servername "$HOST" 2>/dev/null \
  | openssl x509 -noout -dates -subject

keytool inspect local trust (if custom)

keytool -list -v -keystore "$JAVA_HOME/lib/security/cacerts" -storepass changeit | head -n 40

Use only to confirm you are not pinning outdated corporate imports—remote expiry still needs server renew.

Java one-liner repro

jshell <<'EOF'
var c = (javax.net.ssl.HttpsURLConnection)
  new java.net.URL("https://api.example.com/health").openConnection();
c.getResponseCode();
EOF

curl comparison

curl -vI "https://$HOST/health" 2>&1 | grep -Ei 'expire|SSL'

Fix checklist

  1. Prove expired server cert on authority host with OpenSSL externally.
  2. Renew certificate on HTTPS terminator; include required intermediate chain.
  3. Update Java keystore if JVM terminates TLS (Tomcat/NJetty); convert PEM to PKCS12 as needed.
  4. Reload web containers or recycle pods mounting server.ssl.* properties.
  5. Import new intermediates into truststore only when issuer changed—not instead of server renew.
  6. Restart JVM apps to drop connection pools holding failed handshakes.
  7. Re-run integration tests without TrustAll hostname verifiers from test harnesses leaking to prod config.
  8. Monitor hostnames Java clients call via live TLS probes.

Convert renewed PEM to PKCS12 with matching alias entries your Spring server.ssl.key-store-password expects; mismatched alias names survive upload but never bind at runtime, surfacing as protocol or handshake errors distinct from expiry until you read Catalina logs.

When JVMs call mutually authenticated endpoints, rotate server cert first, then schedule client keystore updates before old client cert expiry—otherwise you chase misleading handshake messages. For WebLogic clusters, confirm admin uploads replicate to all managed servers; partial replication mimics expired material on a subset only.

Add synthetic transactions that exercise HTTPS dependencies from the same classpath that production uses (same truststore system properties). Unit tests with Mockito HTTP clients do not validate TLS—the nightly smoke job should fail when openssl shows past notAfter.

Operations leads should coordinate JVM restarts with application owners so connection pools rebuild after server renewal. Announcing only infrastructure complete while apps still pool old handshake failures prolongs mean time to recover. After fix, capture a snippet of successful handshake debug showing fresh notAfter for the ticket and scrub verbose SSL system properties from production startup scripts so logs stay readable.

When to use automation

Nightly batch schedules concentrate JVM handshake failures into one alarming hour that looks like application regression unless dependency TLS is monitored independently. Operations centers should map each batch job to the HTTPS endpoints it calls and attach expiry monitors with the same on call routing used for customer ingress. JVM heavy enterprises benefit from pairing live TLS checks with keytool audits of truststores quarterly, because issuer migrations and manual imports drift over years even when leaf renewals appear automated.

Batch JVM jobs fail at 02:00 without pager context if only business metrics are watched. Schedule TLS checks on each HTTPS dependency in service catalog entries, especially vendor and legacy ESB endpoints.

Use multi-threshold expiry alerts so change windows exist before handshake exceptions flood logs across clustered nodes.

Tag each Spring RestTemplate or WebClient bean configuration with the HTTPS endpoints it calls; import that list into monitoring so JVM batch jobs inherit the same alerts as edge nginx certs. Legacy EJBs and Camel routes hide URLs in XML—grep https:// in deployment descriptors when building the monitor inventory.

Prometheus JMX exporters do not surface remote certificate expiry for partner HTTPS calls your application makes. Rely on blackbox style live probes against those partner URLs instead of inferring TLS health from heap usage or thread counts.

Close Java incidents only after batch queues and interactive servlets both complete sample transactions, because those paths sometimes use different truststore configuration inherited from separate deployment descriptors written years apart. When alerts fire, attach keytool list -v output from the runtime truststore alongside openssl server proof to speed RCA.

Frequently asked questions

Why do only some JVM instances fail?

Different truststores per environment variable, or sticky load balancer to expired backend. Compare openssl against each pool member IP with SNI.

Is disabling certificate validation an option?

TrustAll patterns are unsafe in production. Fix TLS material; keep hostname verification enabled.

Do I need BouncyCastle for modern TLS?

Usually no for expiry. JDK 11+ handles TLS 1.3; focus on cert dates and chain completeness.

Does this affect LDAPS the same way?

Yes—LDAPS is TLS with different port. Renew directory server cert and any intermediate per vendor docs; Java LDAP clients use same JSSE path.

See what the server presents right now

Free live certificate check — no signup.