jwtk/jjwt · error · ExpiredJwtException

JWT expired <differenceMillis> milliseconds ago at <expVal>.

Error message

JWT expired <differenceMillis> milliseconds ago at <expVal>. Current time: <nowVal>. Allowed clock skew: <allowedClockSkewMillis> milliseconds.

What it means

Standard exp-claim enforcement: when parsing a Claims JWT, if the current time (minus allowed clock skew) is past the token's expiration, the parser throws ExpiredJwtException carrying the header, claims, and a message with exact milliseconds-expired, the exp value, current time, and configured clock skew.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:689

            long nowTime = now.getTime();

            // https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4
            // token MUST NOT be accepted on or after any specified exp time:
            Date exp = claims.getExpiration();
            if (exp != null) {

                long maxTime = nowTime - this.allowedClockSkewMillis;
                Date max = allowSkew ? new Date(maxTime) : now;
                if (max.after(exp)) {
                    String expVal = DateFormats.formatIso8601(exp, true);
                    String nowVal = DateFormats.formatIso8601(now, true);

                    long differenceMillis = nowTime - exp.getTime();

                    String msg = "JWT expired " + differenceMillis + " milliseconds ago at " + expVal + ". " +
                            "Current time: " + nowVal + ". Allowed clock skew: " +
                            this.allowedClockSkewMillis + " milliseconds.";
                    throw new ExpiredJwtException(header, claims, msg);
                }
            }

            // https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5
            // token MUST NOT be accepted before any specified nbf time:
            Date nbf = claims.getNotBefore();
            if (nbf != null) {

                long minTime = nowTime + this.allowedClockSkewMillis;
                Date min = allowSkew ? new Date(minTime) : now;
                if (min.before(nbf)) {
                    String nbfVal = DateFormats.formatIso8601(nbf, true);
                    String nowVal = DateFormats.formatIso8601(now, true);

                    long differenceMillis = nbf.getTime() - nowTime;

                    String msg = "JWT early by " + differenceMillis + " milliseconds before " + nbfVal +
                            ". Current time: " + nowVal + ". Allowed clock skew: " +

View on GitHub (pinned to fb71496164)

Solutions

  1. Obtain a fresh token (re-authenticate/refresh) before retrying the parse.
  2. Catch ExpiredJwtException explicitly and trigger your refresh flow instead of treating it as a generic failure.
  3. Call .clockSkewSeconds(...) on the parser if legitimate clock drift between hosts causes near-expiry failures.
  4. Increase the token TTL at issuance if it is genuinely too short for your workload, and never persist tokens longer than their exp.

Example fix

// before
Claims c = Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload();
// after
try {
    Claims c = Jwts.parser().verifyWith(key).clockSkewSeconds(60).build().parseSignedClaims(token).getPayload();
} catch (ExpiredJwtException e) {
    token = refresh();
}
Defensive patterns

Strategy: try-catch

Validate before calling

Claims c = parser.parseSignedClaims(token).getPayload(); // validate first via parseClaimsJws-style check
Date exp = c.getExpiration();
boolean stillValid = exp == null || exp.after(new Date(System.currentTimeMillis() - skewMillis));

Try / catch

try { return parser.parseSignedClaims(token).getPayload(); }
catch (ExpiredJwtException e) { return refreshAndRetry(); }

Prevention

When it happens

Trigger: Parsing any Claims JWT whose exp date is earlier than now minus allowedClockSkewMillis, via parseClaimsJws/parseSignedClaims after signature verification succeeds.

Common situations: Long-running jobs reusing cached tokens past their lifetime, clock drift between issuing and validating servers, tokens issued with too-short TTLs, replayed/stored tokens in logs or DB.

Related errors


AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09). Data as JSON: /api/errors/5d777a703aef2d99. Report an issue: GitHub.