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

  1. Fix the token producer to serialize registered claims per RFC 7519 (NumericDate: seconds since epoch as a number).
  2. Catch MalformedJwtException and log t.getMessage() to identify which claim failed, then inspect the raw payload (base64url-decode the middle segment).
  3. If you control deserialization of foreign tokens, pre-parse the payload yourself and normalize claim types before JJWT parsing.
  4. 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

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


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