jwtk/jjwt · error · IllegalStateException

Encrypted JWTs must have either 'claims' or non-empty 'conte

Error message

Encrypted JWTs must have either 'claims' or non-empty 'content'.

What it means

A JWE's payload is always encoded and protected, but it must contain something: when building an encrypted JWT the builder requires either claims or non-empty content. If both are absent, compact() throws IllegalStateException because an empty encrypted payload is meaningless and not representable.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java:492

        }
    }

    @Override
    public String compact() {

        final boolean jwe = this.enc != null;

        if (jwe && signFunction != null) {
            String msg = "Both 'signWith' and 'encryptWith' cannot be specified. Choose either one.";
            throw new IllegalStateException(msg);
        }

        Payload payload = Assert.stateNotNull(this.payload, "Payload instance null, internal error");
        final Claims claims = this.claimsBuilder.build();

        if (jwe && payload.isEmpty() && Collections.isEmpty(claims)) { // JWE payload can never be empty:
            String msg = "Encrypted JWTs must have either 'claims' or non-empty 'content'.";
            throw new IllegalStateException(msg);
        } // otherwise JWS and Unprotected JWT payloads can be empty

        if (!payload.isEmpty() && !Collections.isEmpty(claims)) {
            throw new IllegalStateException("Both 'content' and 'claims' cannot be specified. Choose either one.");
        }

        if (this.serializer == null) { // try to find one based on the services available
            //noinspection unchecked
            json(Services.get(Serializer.class));
        }

        if (!Collections.isEmpty(claims)) { // normalize so we have one object to deal with:
            payload = new Payload(claims);
        }
        if (compressionAlgorithm != null && !payload.isEmpty()) {
            payload.setZip(compressionAlgorithm);
            this.headerBuilder.put(DefaultHeader.COMPRESSION_ALGORITHM.getId(), compressionAlgorithm.getId());
        }

View on GitHub (pinned to fb71496164)

Solutions

  1. Set claims: Jwts.builder().claims().subject(...).and().encryptWith(...) before compact().
  2. Or set non-empty content: builder.content(byte[]/String payload).
  3. Add an assertion before compact(): ensure the builder has claims or content when encryption is configured.
  4. Catch IllegalStateException and treat it as a programming/configuration bug in the token-building path.

Example fix

// before
Jwts.builder().encryptWith(pubKey, alg, enc).compact(); // empty
// after
Jwts.builder().claims().subject("user").and().encryptWith(pubKey, alg, enc).compact();
Defensive patterns

Strategy: validation

Validate before calling

if (encrypting && claims == null && (content == null || content.length == 0)) throw new IllegalStateException("JWE needs claims or non-empty content");

Try / catch

try { return builder.compact(); } catch (IllegalStateException e) { /* JWE built without payload */ }

Prevention

When it happens

Trigger: Calling compact() after encryptWith(...) without calling claims(...)/setClaims(...) or content(...) with non-empty data.

Common situations: Builders reused across requests where the payload-setting branch was skipped; conditional logic that only sets claims sometimes; refactors that dropped the claims call while keeping encryptWith.

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/8c58fd73e737bc3c. Report an issue: GitHub.