{"record":{"id":"5d777a703aef2d99","repo":"jwtk/jjwt","slug":"jwt-expired-differencemillis-milliseconds-ago-at","errorCode":null,"errorMessage":"JWT expired <differenceMillis> milliseconds ago at <expVal>. Current time: <nowVal>. Allowed clock skew: <allowedClockSkewMillis> milliseconds.","messagePattern":"JWT expired <differenceMillis> milliseconds ago at <expVal>\\. Current time: <nowVal>\\. Allowed clock skew: <allowedClockSkewMillis> milliseconds\\.","errorType":"exception","errorClass":"ExpiredJwtException","httpStatus":null,"severity":"error","filePath":"impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java","lineNumber":689,"sourceCode":"            long nowTime = now.getTime();\n\n            // https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4\n            // token MUST NOT be accepted on or after any specified exp time:\n            Date exp = claims.getExpiration();\n            if (exp != null) {\n\n                long maxTime = nowTime - this.allowedClockSkewMillis;\n                Date max = allowSkew ? new Date(maxTime) : now;\n                if (max.after(exp)) {\n                    String expVal = DateFormats.formatIso8601(exp, true);\n                    String nowVal = DateFormats.formatIso8601(now, true);\n\n                    long differenceMillis = nowTime - exp.getTime();\n\n                    String msg = \"JWT expired \" + differenceMillis + \" milliseconds ago at \" + expVal + \". \" +\n                            \"Current time: \" + nowVal + \". Allowed clock skew: \" +\n                            this.allowedClockSkewMillis + \" milliseconds.\";\n                    throw new ExpiredJwtException(header, claims, msg);\n                }\n            }\n\n            // https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5\n            // token MUST NOT be accepted before any specified nbf time:\n            Date nbf = claims.getNotBefore();\n            if (nbf != null) {\n\n                long minTime = nowTime + this.allowedClockSkewMillis;\n                Date min = allowSkew ? new Date(minTime) : now;\n                if (min.before(nbf)) {\n                    String nbfVal = DateFormats.formatIso8601(nbf, true);\n                    String nowVal = DateFormats.formatIso8601(now, true);\n\n                    long differenceMillis = nbf.getTime() - nowTime;\n\n                    String msg = \"JWT early by \" + differenceMillis + \" milliseconds before \" + nbfVal +\n                            \". Current time: \" + nowVal + \". Allowed clock skew: \" +","sourceCodeStart":671,"sourceCodeEnd":707,"githubUrl":"https://github.com/jwtk/jjwt/blob/fb71496164c71442d08adec4571d9616ed5e1b8d/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java#L671-L707","documentation":"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.","triggerScenarios":"Parsing any Claims JWT whose exp date is earlier than now minus allowedClockSkewMillis, via parseClaimsJws/parseSignedClaims after signature verification succeeds.","commonSituations":"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.","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."],"exampleFix":"// before\nClaims c = Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload();\n// after\ntry {\n    Claims c = Jwts.parser().verifyWith(key).clockSkewSeconds(60).build().parseSignedClaims(token).getPayload();\n} catch (ExpiredJwtException e) {\n    token = refresh();\n}","handlingStrategy":"try-catch","validationCode":"Claims c = parser.parseSignedClaims(token).getPayload(); // validate first via parseClaimsJws-style check\nDate exp = c.getExpiration();\nboolean stillValid = exp == null || exp.after(new Date(System.currentTimeMillis() - skewMillis));","typeGuard":null,"tryCatchPattern":"try { return parser.parseSignedClaims(token).getPayload(); }\ncatch (ExpiredJwtException e) { return refreshAndRetry(); }","preventionTips":["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."],"tags":["jwt","expiration","clock-skew","token-refresh"],"backgroundTag":"jwt-token-expired","analyzedSha":"fb71496164c71442d08adec4571d9616ed5e1b8d","analyzedAt":"2026-09-09T00:33:09.982Z","contentChangedAt":"2026-09-09T00:33:09.982Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}