jwtk/jjwt · error · UnsupportedJwtException
Protected Header crit set references unsupported header name
Error message
Protected Header crit set references unsupported header name '%s'. Application developers expecting to support a JWT extension using header '%s' in their application code must indicate it is supported by using the JwtParserBuilder.critical method. Header: %s
What it means
For each name in a protected header's 'crit' set, the parser also requires that the application has explicitly declared support via JwtParserBuilder.critical(...). RFC 7515 mandates that crit-protected extensions must be understood; jjwt enforces this by throwing UnsupportedJwtException when a crit name is not in the parser's supported-critical set, even if the header contains the parameter.
Source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:449
Set<String> supportedCrit = this.critical;
String b64Id = DefaultJwsHeader.B64.getId();
if (!unencodedPayload.isEmpty() && !this.critical.contains(b64Id)) {
// The application developer explicitly indicates they're using a B64 payload, so
// ensure that the B64 crit header is supported, even if they forgot to configure it on the
// parser builder:
supportedCrit = new LinkedHashSet<>(Collections.size(this.critical) + 1);
supportedCrit.add(DefaultJwsHeader.B64.getId());
supportedCrit.addAll(this.critical);
}
// assert any values per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11:
for (String name : crit) {
if (!header.containsKey(name)) {
String msg = String.format(CRIT_MISSING_MSG, name, name, header);
throw new MalformedJwtException(msg);
}
if (!supportedCrit.contains(name)) {
String msg = String.format(CRIT_UNSUPPORTED_MSG, name, name, header);
throw new UnsupportedJwtException(msg);
}
}
}
// =============== Payload =================
final CharSequence payloadToken = tokenized.getPayload();
Payload payload;
boolean integrityVerified = false; // only true after successful signature verification or AEAD decryption
// check if b64 extension enabled:
final boolean payloadBase64UrlEncoded = !(header instanceof JwsHeader) || ((JwsHeader) header).isPayloadEncoded();
if (payloadBase64UrlEncoded) {
// standard encoding, so decode it:
byte[] data = decode(payloadToken, "payload");
payload = new Payload(data, header.getContentType());
} else {
// The JWT uses the b64 extension, and we already know the parser supports that extension at this point
// in the code execution path because of the ----- crit ----- assertions section above as well as theView on GitHub (pinned to fb71496164)
Solutions
- Register the extension on the parser builder: Jwts.parser().critical().add("name").and() (and implement/verify its semantics).
- For unencoded-payload JWSs, ensure the parser is built to accept 'b64' as critical and use parseSignedContent(byte[] payload).
- Coordinate with the token issuer to confirm which crit extensions are in use and document them in all consumers.
- If the extension is not needed, remove it from the token's crit set at the producer.
Example fix
// before
JwtParser p = Jwts.parser().verifyWith(key).build();
p.parseSignedClaims(token); // header has crit:["b64"]
// after
JwtParser p = Jwts.parser().verifyWith(key)
.critical().add("b64").and().build(); Defensive patterns
Strategy: validation
Validate before calling
java.util.Map<String,Object> h = parseHeaderJson(token);
java.util.Set<String> supported = java.util.Set.of("b64"); // extensions you actually implement
@SuppressWarnings("unchecked") java.util.List<String> crit = (java.util.List<String>) h.get("crit");
if (crit != null && !supported.containsAll(crit)) throw new IllegalArgumentException("unsupported crit extensions: " + crit); Try / catch
try { return parser.parse(token); }
catch (io.jsonwebtoken.UnsupportedJwtException e) { throw new InvalidTokenException("unsupported crit extension", e); } Prevention
- Declare every crit extension you support via JwtParserBuilder.critical(...)
- Keep the list of supported crit names in one shared constant across services
- When issuers add extensions, update all consumers' parser builders
- Only register extensions whose semantics you actually enforce
When it happens
Trigger: Parsing a token whose header contains "crit":["name"] (with "name" present in the header) while the parser was built without .critical("name") — common with the 'b64' unencoded-payload extension or custom extension headers.
Common situations: Receiving tokens using the b64/unencoded-payload extension without configuring the parser; adding custom crit headers on the producer side but not registering them in every consuming service; version upgrades where new crit extensions appear from an issuer.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Unsecured JWSs (those with an alg header value of 'none') ma
- Protected Header crit set references header name '%s', but t
- Unexpected content JWS.
- Unexpected Claims JWS.
- Both 'signWith' and 'encryptWith' cannot be specified. Choos
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/cba5b85c7a02467c.
Report an issue: GitHub.