jwtk/jjwt · error · MalformedJwtException

Compact JWE strings MUST always contain a payload (ciphertex

Error message

Compact JWE strings MUST always contain a payload (ciphertext).

What it means

Unlike JWS payloads, which may legally be empty, a compact JWE must always contain ciphertext in its payload segment. jjwt throws MalformedJwtException when a TokenizedJwe has an empty payload, because encryption of nothing cannot be represented in compact serialization here.

Source

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

            if (Strings.hasText(payloadToken)) {
                // we need to verify what was in the token, otherwise it'd be a security issue if we ignored it
                // and assumed the (likely safe) unencodedPayload value instead:
                payload = new Payload(payloadToken, header.getContentType());
            } else {
                //no payload token (a detached payload), so we need to ensure that they've specified the payload value:
                if (unencodedPayload.isEmpty()) {
                    String msg = String.format(B64_MISSING_PAYLOAD, header);
                    throw new SignatureException(msg);
                }
                // otherwise, use the specified payload:
                payload = unencodedPayload;
            }
        }

        if (tokenized instanceof TokenizedJwe && payload.isEmpty()) {
            // Only JWS payload can be empty per https://github.com/jwtk/jjwt/pull/540
            String msg = "Compact JWE strings MUST always contain a payload (ciphertext).";
            throw new MalformedJwtException(msg);
        }

        byte[] iv = null;
        byte[] digest = null; // either JWE AEAD tag or JWS signature after Base64Url-decoding
        if (tokenized instanceof TokenizedJwe) {

            TokenizedJwe tokenizedJwe = (TokenizedJwe) tokenized;
            JweHeader jweHeader = Assert.stateIsInstance(JweHeader.class, header, "Not a JweHeader. ");

            // Ensure both an 'alg' and 'enc' header value exists and is supported before spending time/effort
            // base64Url-decoding anything:
            final AeadAlgorithm encAlg = this.encAlgs.apply(jweHeader);
            Assert.stateNotNull(encAlg, "JWE Encryption Algorithm cannot be null.");
            @SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgs.apply(jweHeader);
            Assert.stateNotNull(keyAlg, "JWE Key Algorithm cannot be null.");

            byte[] cekBytes = Bytes.EMPTY; //ignored unless using an encrypted key algorithm
            CharSequence base64Url = tokenizedJwe.getEncryptedKey();

View on GitHub (pinned to fb71496164)

Solutions

  1. Re-issue the JWE with non-empty content (an empty claims map still produces valid ciphertext).
  2. Verify the compact string has all five non-empty JWE segments before parsing.
  3. Fix the producer so it never serializes an empty ciphertext segment; if there is truly nothing to encrypt, use a signed JWS instead.
  4. Check for transport truncation if the token appears cut off.

Example fix

// before: JWE with empty ciphertext
String jwe = h + "." + k + "." + iv + "." + "" + "." + tag;

// after: always encrypt something (e.g. empty claims map)
String jwe = Jwts.builder().claims(Collections.emptyMap())
    .encryptWith(key, Jwts.KEY.A128KW, Jwts.ENC.A128GCM).compact();
Defensive patterns

Strategy: validation

Validate before calling

String[] parts = jwe.split("\\.", -1);
if (parts.length == 5 && parts[3].isEmpty()) throw new IllegalArgumentException("JWE ciphertext segment is empty");

Try / catch

try { return parser.parse(jwe); }
catch (io.jsonwebtoken.MalformedJwtException e) { throw new InvalidTokenException("JWE payload empty", e); }

Prevention

When it happens

Trigger: Calling parse() on a JWE compact string whose fourth segment (ciphertext) is empty — e.g. header.key.iv..tag or a truncated string.

Common situations: Attempting to 'encrypt' an empty body and producing a malformed token; truncation or corruption removing the ciphertext segment; copying 4 of 5 segments; custom serializers mishandling empty claims maps.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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