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

  1. Inspect the token's first segment (base64url-decode it) to see the actual header JSON and fix the issuer that produced it
  2. Catch MalformedJwtException around parse() and reject the token as untrusted
  3. 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

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


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