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"); } }} 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
CertificateFactory.getInstance("X.509").generateCertificatereads PEM and DER;getNotAfter()returns ajava.util.Date, andtoInstant()gives the exact UTC instant (2035-12-31T23:59:59Z).Duration.between(Instant.now(), end).toDays()gives the days left; it is negative once the certificate has expired.- For a server, an
SSLContextbuilt from aTrustManagerFactoryover a key store holding onlyroot.pemreplaces the JDK'scacertsfor that client. 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 2025here); convert withtoInstant()before formatting or comparing.checkValidity()without arguments compares with the current time and throwsCertificateExpiredException: NotAfter: …— fine for a yes/no check, but a renewal reminder needsgetNotAfter()and a threshold.