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
- Obtain a fresh token (re-authenticate/refresh) before retrying the parse.
- Catch ExpiredJwtException explicitly and trigger your refresh flow instead of treating it as a generic failure.
- Call .clockSkewSeconds(...) on the parser if legitimate clock drift between hosts causes near-expiry failures.
- 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
- Always catch ExpiredJwtException separately from JwtException to drive refresh flows.
- Configure clockSkewSeconds(...) proportional to real host clock drift.
- Refresh tokens proactively before exp (e.g. at 80% of TTL) instead of waiting for failure.
- Never cache parsed token results longer than the token's remaining lifetime.
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
- JWT early by <differenceMillis> milliseconds before <nbfVal>
- Unexpected unsecured Claims JWT.
- Unexpected content JWS.
- Unexpected Claims JWS.
- Unexpected content JWE.
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/5d777a703aef2d99.
Report an issue: GitHub.