jwtk/jjwt · error · MalformedJwtException

JWE header does not contain a required 'alg' (Algorithm)…

Error message

JWE header does not contain a required 'alg' (Algorithm) header parameter.  This header parameter is mandatory per the JWE Specification, Section 4.1.1. See https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.1 for more information.

What it means

The JWE specification (RFC 7516 §4.1.1) mandates an 'alg' (Algorithm) header parameter identifying the key-management algorithm. When parsing a tokenized JWE whose header lacks a usable 'alg' value, parse() throws MalformedJwtException with this message (distinguished from the JWS variant via MISSING_JWE_ALG_MSG).

Solutions

  1. Fix the token producer to include 'alg' in the protected header (e.g. Jwts.builder().header().algorithm(...)).encryptWith(...)
  2. Decode the first segment of the failing token to confirm 'alg' is missing and reject such tokens at the boundary
  3. If another library produced the token, configure it to put alg/enc in the protected header per RFC 7516

Example fix

// before
String jwe = Jwts.builder().setClaims(claims)
    .encryptWith(key, encAlg).compact(); // no alg header
// after
String jwe = Jwts.builder().setClaims(claims)
    .header().add("alg", keyAlg.getId()).and()
    .encryptWith(key, keyAlg, encAlg).compact();
Defensive patterns

Strategy: validation

Validate before calling

String headerJson = new String(Base64.getUrlDecoder().decode(token.split("\\.")[0]), StandardCharsets.UTF_8);
if (!headerJson.contains("\"alg\"")) {
    throw new MalformedJwtException("JWE header missing required 'alg'");
}

Try / catch

try {
    return parser.parseEncryptedClaims(token, decryptKey);
} catch (MalformedJwtException e) {
    if (e.getMessage().contains("'alg'")) {
        throw new UnauthorizedException("Non-compliant JWE: missing alg", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Parsing an encrypted JWT (five-segment compact form) whose protected header omits 'alg' or has an empty/whitespace-only value — typically a token produced by a non-compliant encryptor or a hand-assembled header.

Common situations: Custom/buggy encryption code building headers manually; other libraries that place 'alg' in the unprotected (shared) header instead of the protected header; tokens mutated so header fields were dropped.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

        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) {
            if (tokenized instanceof TokenizedJwe) {
                throw new MalformedJwtException(JWE_NONE_MSG);
            }
            // Unsecured JWTs are disabled by default per the RFC:
            if (!this.unsecured) {
                String msg = UNSECURED_DISABLED_MSG_PREFIX + header;
                throw new UnsupportedJwtException(msg);
            }
            if (hasDigest) {
                throw new MalformedJwtException(JWS_NONE_SIG_MISMATCH_MSG);
            }
            if (header.containsKey(DefaultProtectedHeader.CRIT.getId())) {

View on GitHub (pinned to fb71496164)