jwtk/jjwt · error · java.lang.UnsupportedOperationException

${getName()} instance is immutable and may not be modified.

Error message

${getName()} instance is immutable and may not be modified.

What it means

ParameterMap (used for JWT/Claims/Jws header and claims collections) becomes immutable once finalized/initialized; afterwards any mutating call (put, remove, clear) is rejected with UnsupportedOperationException. The library protects header/claims instances handed to the user so parsed tokens cannot be modified in place.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/ParameterMap.java:80

    }

    public ParameterMap(Registry<String, ? extends Parameter<?>> registry, Map<String, ?> values, boolean mutable) {
        Assert.notNull(registry, "Parameter registry cannot be null.");
        Assert.notEmpty(registry.values(), "Parameter registry cannot be empty.");
        this.PARAMS = registry;
        this.values = new LinkedHashMap<>();
        this.idiomaticValues = new LinkedHashMap<>();
        if (!Collections.isEmpty(values)) {
            putAll(values);
        }
        this.mutable = mutable;
        this.initialized = true;
    }

    private void assertMutable() {
        if (initialized && !mutable) {
            String msg = getName() + " instance is immutable and may not be modified.";
            throw new UnsupportedOperationException(msg);
        }
    }

    protected ParameterMap replace(Parameter<?> param) {
        Registry<String, ? extends Parameter<?>> registry = Parameters.replace(this.PARAMS, param);
        return new ParameterMap(registry, this, this.mutable);
    }

    @Override
    public String getName() {
        return "Map";
    }

    @Override
    public <T> T get(Parameter<T> param) {
        Assert.notNull(param, "Parameter cannot be null.");
        final String id = Assert.hasText(param.getId(), "Parameter id cannot be null or empty.");
        Object value = idiomaticValues.get(id);

View on GitHub (pinned to fb71496164)

Solutions

  1. Create a new mutable map: new HashMap<>(claims), mutate the copy, and build a new JWT with Jwts.builder().setClaims(copy)
  2. Use Jwts.builder() to construct modified tokens instead of mutating parsed ones
  3. If constructing your own ParameterMap, keep it mutable (mutable=true) until you intentionally finalize it

Example fix

// before
Claims claims = Jwts.parser().verifyWith(key).parseClaimsJws(jwt).getBody();
claims.put("role", "admin"); // UnsupportedOperationException
// after
Claims claims = Jwts.parser().verifyWith(key).parseClaimsJws(jwt).getBody();
Map<String, Object> updated = new HashMap<>(claims);
updated.put("role", "admin");
String newJwt = Jwts.builder().setClaims(updated).signWith(key).compact();
Defensive patterns

Strategy: type-guard

Validate before calling

// copy before mutating
Map<String, Object> editable = new HashMap<>(jws.getHeader());
editable.put("typ", "JWT");

Type guard

boolean isMutable(ParameterMap map) {
    try { map.isEmpty(); return true; } catch (UnsupportedOperationException e) { return false; }
}

Try / catch

try {
    claims.put("role", "admin");
} catch (UnsupportedOperationException e) {
    claims = Jwts.claims(new HashMap<>(claims)); // rebuild mutable instance
}

Prevention

When it happens

Trigger: Calling put/remove/clear on a Jws.getHeader(), Claims instance obtained from a parsed token, or any ParameterMap after it has been initialized as immutable (e.g. the claims returned by parseClaimsJws).

Common situations: Trying to add a claim to a parsed token's Claims before re-signing; mutating header maps returned from Jws; building code that assumes mutable Maps because Claims extends Map.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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