jwtk/jjwt · error · MalformedJwtException
Invalid protected header: ${e.getMessage()}
Error message
Invalid protected header: ${e.getMessage()} What it means
The Base64Url-decoded protected header bytes could not be turned into a Header instance — either the JSON did not deserialize into the expected structure or TokenizedJws.createHeader rejected its contents (e.g. missing/invalid required fields). parse() wraps any such exception in a MalformedJwtException with this message.
Source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:389
Assert.stateNotNull(unencodedPayload, "internal error: unencodedPayload is null.");
final TokenizedJwt tokenized = jwtTokenizer.tokenize(compact);
final CharSequence base64UrlHeader = tokenized.getProtected();
if (!Strings.hasText(base64UrlHeader)) {
String msg = "Compact JWT strings MUST always have a Base64Url protected header per " +
"https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4).";
throw new MalformedJwtException(msg);
}
// =============== Header =================
final byte[] headerBytes = decode(base64UrlHeader, "protected header");
Map<String, ?> m = deserialize(Streams.of(headerBytes), "protected header");
Header header;
try {
header = tokenized.createHeader(m);
} catch (Exception e) {
String msg = "Invalid protected header: " + e.getMessage();
throw new MalformedJwtException(msg, e);
}
// https://tools.ietf.org/html/rfc7515#section-10.7 , second-to-last bullet point, note the use of 'always':
//
// * Require that the "alg" Header Parameter be carried in the JWS
// Protected Header. (This is always the case when using the JWS
// Compact Serialization and is the approach taken by CMS [RFC6211].)
//
final String alg = Strings.clean(header.getAlgorithm());
if (!Strings.hasText(alg)) {
String msg = tokenized instanceof TokenizedJwe ? MISSING_JWE_ALG_MSG : MISSING_JWS_ALG_MSG;
throw new MalformedJwtException(msg);
}
final boolean unsecured = Jwts.SIG.NONE.getId().equalsIgnoreCase(alg);
final CharSequence base64UrlDigest = tokenized.getDigest();
final boolean hasDigest = Strings.hasText(base64UrlDigest);
if (unsecured) {View on GitHub (pinned to fb71496164)
Solutions
- Inspect the token's first segment (base64url-decode it) to see the actual header JSON and fix the issuer that produced it
- Catch MalformedJwtException around parse() and reject the token as untrusted
- Ensure all token producers use standard JWT serialization (JSON object header with string 'alg')
Example fix
// before
Claims c = Jwts.parser().verifyWith(key).build().parseSignedClaims(rawToken).getBody(); // throws
// after
try {
Claims c = Jwts.parser().verifyWith(key).build().parseSignedClaims(rawToken).getBody();
} catch (MalformedJwtException e) {
log.warn("Rejected malformed JWT: {}", e.getMessage());
throw new UnauthorizedException();
} Defensive patterns
Strategy: try-catch
Try / catch
try {
return parser.parseSignedClaims(token);
} catch (MalformedJwtException e) {
if (e.getMessage().startsWith("Invalid protected header")) {
log.warn("JWT header not deserializable: {}", e.getMessage());
}
throw new UnauthorizedException(e);
} Prevention
- Use standard JWT libraries for token production (never hand-roll headers)
- Base64url-decode the header in tests to validate issuer output
- Reject tokens at ingress with a broad MalformedJwtException catch
When it happens
Trigger: Header JSON is structurally invalid (not an object, wrong types for fields like 'alg' not a string), or header bytes decode to garbage that fails Jackson deserialization, causing createHeader to throw.
Common situations: Hand-crafted or fuzzed tokens; tokens produced by buggy/other JWT implementations emitting non-JSON headers; corruption of the header segment in transit; decoding mistakes yielding non-UTF8 bytes.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- Unable to deserialize: ${t.getMessage()}
- Unable to serialize object of type ${className}: ${e.getMess
- Malformed ${name} JSON: ${t.getMessage()}
- Cannot serialize ${name} to JSON. Cause: ${t.getMessage()}
- ${String.format(MALFORMED_COMPLEX_ERROR, this.name, this.nam
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/b55aadfe263bd6c0.
Report an issue: GitHub.