jwtk/jjwt · error · IncorrectClaimException

JWT Claim '<expectedClaimName>' was expected to be a Date, b

Error message

JWT Claim '<expectedClaimName>' was expected to be a Date, but its value cannot be converted to a Date using current heuristics.  Value: <actualClaimValue>

What it means

Thrown as IncorrectClaimException by require(...) claim validation when the expected claim value passed to the parser is a Date (or Calendar), but the actual claim in the token cannot be converted to a Date using JJWT's heuristics (long epoch millis, ISO-8601 string, etc.). This means a requiredDate(...) call found a claim whose value is of an unparseable type/format.

Source

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

        return o;
    }

    private void validateExpectedClaims(Header header, Claims claims) {

        final Claims expected = expectedClaims.build();

        for (String expectedClaimName : expected.keySet()) {

            Object expectedClaimValue = normalize(expected.get(expectedClaimName));
            Object actualClaimValue = normalize(claims.get(expectedClaimName));

            if (expectedClaimValue instanceof Date) {
                try {
                    actualClaimValue = claims.get(expectedClaimName, Date.class);
                } catch (Exception e) {
                    String msg = "JWT Claim '" + expectedClaimName + "' was expected to be a Date, but its value " +
                            "cannot be converted to a Date using current heuristics.  Value: " + actualClaimValue;
                    throw new IncorrectClaimException(header, claims, expectedClaimName, expectedClaimValue, msg);
                }
            }

            if (actualClaimValue == null) {
                boolean collection = expectedClaimValue instanceof Collection;
                String msg = "Missing '" + expectedClaimName + "' claim. Expected value";
                if (collection) {
                    msg += "s: " + expectedClaimValue;
                } else {
                    msg += ": " + expectedClaimValue;
                }
                throw new MissingClaimException(header, claims, expectedClaimName, expectedClaimValue, msg);
            } else if (expectedClaimValue instanceof Collection) {
                Collection<?> expectedValues = (Collection<?>) expectedClaimValue;
                Collection<?> actualValues = actualClaimValue instanceof Collection ? (Collection<?>) actualClaimValue :
                        Collections.setOf(actualClaimValue);
                for (Object expectedValue : expectedValues) {
                    if (!Collections.contains(actualValues.iterator(), expectedValue)) {

View on GitHub (pinned to fb71496164)

Solutions

  1. Fix the token producer to emit the claim as epoch-millis (long) or ISO-8601 date string.
  2. Inspect the actual token payload (base64-decode the claims section) to see the raw value and adjust your expectation type.
  3. If the claim is genuinely not a date, change the require() call to require(name, correctExpectedValue) of matching type.
  4. Add a custom claim converter/serializer on the issuing side so dates round-trip in a standard format.

Example fix

// before
String jwt = Jwts.builder().claim("iat", "yesterday").compact(); // unparseable
Claims c = Jwts.parser().build().parseSignedClaims(jwt).getPayload();
Jwts.parser().require("iat", new Date()).build().parse(jwt); // IncorrectClaimException
// after
String jwt = Jwts.builder().claim("iat", System.currentTimeMillis()).compact();
Jwts.parser().require("iat", new Date()).build().parse(jwt); // OK
Defensive patterns

Strategy: validation

Validate before calling

Object raw = io.jsonwebtoken.Jwts.parser().build().parseUnsecuredClaims(jwt).getPayload().get("iat");
boolean isDateLike = raw instanceof Number ||
    (raw instanceof String s && s.matches("\\d{10,13}|\\d{4}-\\d{2}-\\d{2}T.*"));
if (!isDateLike) throw new IllegalArgumentException("Claim 'iat' is not date-like: " + raw);

Type guard

boolean isDateLike(Object v) {
    return v instanceof Date || v instanceof Number
        || (v instanceof String s && (s.matches("\\d+") || s.matches("\\d{4}-\\d{2}-\\d{2}T.+")));
}

Try / catch

try {
    Jwts.parser().require("exp", expiryDate).build().parse(jwt);
} catch (io.jsonwebtoken.IncorrectClaimException e) {
    // inspect e.getClaimName()/value; producer emitted a non-date format
}

Prevention

When it happens

Trigger: parser.require(claimName, someDate) where the JWT contains claimName with a value that is neither a numeric epoch-millis nor a recognized date string (e.g. an arbitrary object, boolean, or malformed text).

Common situations: Issuer serializes dates as non-standard formats (e.g. '01/02/2024', human-readable text, or nested objects); a JWT library on the producing side emits dates as seconds-instead-of-millis strings with non-numeric characters; claims populated programmatically with wrong types.

Related errors


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