jwtk/jjwt · error · MalformedJwtException
Invalid claims: + t.getMessage()
Error message
Invalid claims: + t.getMessage()
What it means
After integrity verification, the parser converts the JWT payload map into a DefaultClaims object. If the map contains values that violate the Claims contract (bad types for standard claims like exp/iat/nbf, non-string iss/sub, etc.), the constructor throws and the parser wraps it as a MalformedJwtException with the underlying message.
Source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:630
// mark/reset isn't possible, we'll need to buffer:
if (!in.markSupported()) {
in = new BufferedInputStream(in);
in.mark(0);
}
claimsMap = deserialize(new UncloseableInputStream(in) /* Don't close in case we need to rewind */, "claims");
} catch (DeserializationException |
MalformedJwtException ignored) { // not JSON, treat it as a byte[]
// String msg = "Invalid claims: " + e.getMessage();
// throw new MalformedJwtException(msg, e);
} finally {
Streams.reset(in);
}
if (claimsMap != null) {
try {
claims = new DefaultClaims(claimsMap);
} catch (Throwable t) {
String msg = "Invalid claims: " + t.getMessage();
throw new MalformedJwtException(msg);
}
}
}
if (claims == null) {
// consumable, but not claims, so convert to byte array:
payloadBytes = Streams.bytes(in, "Unable to convert payload to byte array.");
}
} finally { // always ensure closed per https://github.com/jwtk/jjwt/issues/949
Objects.nullSafeClose(in);
}
}
// =============== Post-SKR Signature Check =================
if (hasDigest && signingKeyResolver != null) { // TODO: remove for 1.0
// A SigningKeyResolver has been configured, and due to it's API, we have to verify the signature after
// parsing the body. This can be a security risk, so it needs to be removed before 1.0
JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. ");
digest = verifySignature(tokenized, jwsHeader, alg, this.signingKeyResolver, claims, payload);View on GitHub (pinned to fb71496164)
Solutions
- Fix the token producer to serialize registered claims per RFC 7519 (NumericDate: seconds since epoch as a number).
- Catch MalformedJwtException and log t.getMessage() to identify which claim failed, then inspect the raw payload (base64url-decode the middle segment).
- If you control deserialization of foreign tokens, pre-parse the payload yourself and normalize claim types before JJWT parsing.
- Upgrade JJWT if an older version rejects a valid claim layout fixed in later releases.
Example fix
// before (producer)
claims.put("exp", Instant.now().plus(1, HOURS).toString());
// after
claims.setExpiration(Date.from(Instant.now().plus(1, HOURS))); Defensive patterns
Strategy: try-catch
Validate before calling
// pre-validate registered claim types on the raw payload map
Object exp = claimsMap.get("exp");
if (exp != null && !(exp instanceof Number)) throw new IllegalArgumentException("exp must be NumericDate (number)"); Try / catch
try { parser.parseSignedClaims(token); } catch (MalformedJwtException e) { log.warn("Invalid claims payload: {}", e.getMessage()); /* decode payload manually to inspect */ } Prevention
- Use JJWT's ClaimsBuilder/ JwtBuilder to mint tokens so claim types are always correct.
- When interoperating with other libraries, check how they serialize exp/iat/nbf (must be numeric seconds).
- Round-trip test cross-language token exchange in CI.
- Never put null values into reserved claim names.
When it happens
Trigger: Parsing a JWT whose claims map has wrong-typed or invalid registered claims — e.g. exp as a string instead of a numeric date, null reserved claim values, nested structures DefaultClaims rejects.
Common situations: Tokens minted by other libraries (e.g. emitting ISO date strings for exp), tokens serialized with a generic JSON mapper losing numeric types, proxy/middleware rewriting claim values to strings.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Unsupported value type. Expected: ${type.getName()}, found:
- Unexpected Claims JWS.
- Unexpected Claims JWE.
- ${message}Object of class [${objClassName}] must be an insta
- ${message}${subType} is not assignable to ${superType}
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/4245f0459b86d703.
Report an issue: GitHub.