jwtk/jjwt · error · MalformedJwtException

Compact JWT strings MUST always have a Base64Url protected h

Error message

Compact JWT strings MUST always have a Base64Url protected header per https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4).

What it means

Every compact JWT must contain a Base64Url-encoded protected header as its first dot-separated segment per RFC 7519 §7.2. When tokenize() splits the compact string and the protected header segment is empty/blank, parse() throws MalformedJwtException.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:378

    }

    @Override
    public Jwt<?, ?> parse(Reader reader) {
        Assert.notNull(reader, "Reader cannot be null.");
        return parse(reader, Payload.EMPTY);
    }

    private Jwt<?, ?> parse(Reader compact, Payload unencodedPayload) {

        Assert.notNull(compact, "Compact reader cannot be null.");
        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].)

View on GitHub (pinned to fb71496164)

Solutions

  1. Check the token is a complete three- (or five-) segment compact JWT before parsing
  2. Strip any 'Bearer ' prefix and trim whitespace from the credential value
  3. Fix upstream code (storage/logging) that truncates or loses the token

Example fix

// before
String token = authHeader.split(" ")[1]; // could yield partial token
// after
String token = authHeader.replaceFirst("^Bearer\\s+", "").trim();
if (token.chars().filter(c -> c == '.').count() != 2) throw new MalformedJwtException("bad token");
Defensive patterns

Strategy: validation

Validate before calling

if (token == null || token.chars().filter(c -> c == '.').count() != 2) {
    throw new MalformedJwtException("Compact JWT must have 3 dot-separated segments");
}

Try / catch

try {
    return parser.parseSignedClaims(token);
} catch (MalformedJwtException e) {
    throw new UnauthorizedException("Malformed JWT", e);
}

Prevention

When it happens

Trigger: Calling parse/parseSignedClaims/parseSignedContent with a string missing the header segment: e.g. ".payload.signature", "..", a bare signature, an empty string, or a truncated token cut at the first dot.

Common situations: Tokens truncated during logging/storage; string splitting or slicing bugs; passing authorization header values like 'Bearer ' prefix remnants or only the signature; reading tokens from config fields that are empty.

Related errors


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