apereo/cas · error · IllegalArgumentException
JWT time claim is invalid
Error message
JWT time claim is invalid
What it means
readNumericDate converts a JWT time claim (exp/nbf/iat) to an Instant via BigDecimal epoch-second parsing, including nanoseconds. A non-numeric or numerically infeasible value raises ArithmeticException, which is rethrown as IllegalArgumentException("JWT time claim is invalid").
Solutions
- Fix the JWT producer to emit NumericDate epoch seconds (possibly fractional) for time claims
- Validate the claim value on the client before signing
- Decode the token and inspect the raw exp/nbf/iat values with a JWT debugger
Example fix
// before
claims.setExpiration(new Date("2026-09-08")); // formatted date, not NumericDate
// after
claims.setExpiration(new Date(System.currentTimeMillis() + 3600_000L)); // epoch millis -> NumericDate Defensive patterns
Strategy: validation
Validate before calling
Object v = claims.get("exp");
boolean ok = (v instanceof Number) || (v instanceof String s && s.matches("-?\\d+(\\.\\d+)?")); Try / catch
try { Instant exp = controllerReadNumericDate(claims); }
catch (IllegalArgumentException e) { logger.warn("Invalid time claim format"); rejectToken(); } Prevention
- Always emit NumericDate (epoch seconds) for exp/nbf/iat
- Unit-test JWT builders for claim types
- Validate token payload shapes in CI
When it happens
Trigger: A time claim whose string representation cannot be converted to epoch seconds — e.g. a non-numeric string like 'now', a value exceeding long range, or a fractional part with too many digits for longValueExact/movePointRight conversion.
Common situations: Clients placing formatted dates ('2026-01-01T00:00:00Z') instead of NumericDate epoch seconds in exp/nbf/iat; keys generating oversized numeric claims; corrupted JWT payloads.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- JWT audience is invalid
- JWT string claim is missing or invalid
- Unable to accept the ID token with an invalid [sub] claim
- Unknown authorization header type
- Token has expired: and is after
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/2abc821ea9944636.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/vc/presentation/OidcVerifiableCredentialPresentationResponseEndpointController.java:451
"Key binding JWT predates the presentation transaction");
}
private static @Nullable Instant readNumericDate(final Map<String, Object> claims,
final String name,
final boolean required) {
val value = claims.get(name);
require(value != null || !required, "JWT is missing a required time claim");
if (value == null) {
return null;
}
require(value instanceof Number, "JWT time claim is invalid");
try {
val numericDate = new BigDecimal(value.toString());
val components = numericDate.divideAndRemainder(BigDecimal.ONE);
return Instant.ofEpochSecond(components[0].longValueExact(),
components[1].movePointRight(9).longValueExact());
} catch (final ArithmeticException exception) {
throw new IllegalArgumentException("JWT time claim is invalid", exception);
}
}
private static List<String> readAudience(final Map<String, Object> claims) {
val audience = claims.get("aud");
if (audience instanceof final String value) {
return List.of(value);
}
if (audience instanceof final List<?> values
&& values.stream().allMatch(String.class::isInstance)) {
return values.stream().map(String.class::cast).toList();
}
throw new IllegalArgumentException("JWT audience is invalid");
}
private static String requiredStringClaim(final Map<String, Object> claims, final String name) {
val value = claims.get(name);
if (!(value instanceof final String stringValue) || stringValue.isBlank()) {View on GitHub (pinned to e7288fc434)