Skip to content
ecdsa.com

Certificate expiry · Java

How to check SSL certificate expiration date in Java

To check SSL certificate expiration date in Java, read getNotAfter() from an X509Certificate — loaded from a file with CertificateFactory, or taken from a server's TLS session with java.net.http.HttpClient. This recipe does both with the JDK only, trusting a private test root, and shows the exception a verifying client gets from an expired certificate.

Tested with OpenJDK 25.0.4.1 (Eclipse Temurin 25.0.4.1+1, java.net.http.HttpClient) and OpenSSL 3.0.13 (openssl s_server as the local TLS servers).

CertExpiry.java — notAfter and days left for PEM or DER files
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
public class CertExpiry {
static Instant notAfter(Path path) throws Exception {
try (InputStream in = Files.newInputStream(path)) { // PEM or DER
X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in);
return cert.getNotAfter().toInstant(); // java.util.Date to an exact UTC instant
}
}
public static void main(String[] args) throws Exception {
for (String file : args) {
Instant end = notAfter(Path.of(file));
long daysLeft = Duration.between(Instant.now(), end).toDays();
System.out.println(file + ": notAfter " + end + ", " + daysLeft + " days left");
}
}
}
ServerExpiry.java — the expiry of the certificate an HTTPS server presents
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
public class ServerExpiry {
public static void main(String[] args) throws Exception {
// Trust our own root for this client instead of the JDK's cacerts
KeyStore trust = KeyStore.getInstance("PKCS12");
trust.load(null, null);
try (InputStream in = Files.newInputStream(Path.of("root.pem"))) {
trust.setCertificateEntry("root", CertificateFactory.getInstance("X.509").generateCertificate(in));
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trust);
SSLContext tls = SSLContext.getInstance("TLS");
tls.init(null, tmf.getTrustManagers(), null);
try (HttpClient client = HttpClient.newBuilder().sslContext(tls).build()) {
HttpResponse<Void> response = client.send(
HttpRequest.newBuilder(URI.create(args[0])).build(), HttpResponse.BodyHandlers.discarding());
X509Certificate leaf = (X509Certificate) response.sslSession().orElseThrow().getPeerCertificates()[0];
System.out.println(args[0] + " notAfter " + leaf.getNotAfter().toInstant());
}
}
}

How it works

  1. CertificateFactory.getInstance("X.509").generateCertificate reads PEM and DER; getNotAfter() returns a java.util.Date, and toInstant() gives the exact UTC instant (2035-12-31T23:59:59Z).
  2. Duration.between(Instant.now(), end).toDays() gives the days left; it is negative once the certificate has expired.
  3. For a server, an SSLContext built from a TrustManagerFactory over a key store holding only root.pem replaces the JDK's cacerts for that client.
  4. HttpResponse.sslSession() exposes the TLS session of the request; getPeerCertificates()[0] is the server's leaf certificate, already verified, host name included.

Gotchas

  • A verifying client cannot read the date of an expired server certificate: the handshake fails with SSLHandshakeException: (certificate_expired) PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed.
  • getNotAfter() prints in the JVM's default time zone (Mon Mar 31 23:59:59 UTC 2025 here); convert with toInstant() before formatting or comparing.
  • checkValidity() without arguments compares with the current time and throws CertificateExpiredException: NotAfter: … — fine for a yes/no check, but a renewal reminder needs getNotAfter() and a threshold.

Related recipes