jwtk/jjwt · error · java.lang.IllegalArgumentException

JWK map keys must be Strings. Encountered key '${key}' of ty

Error message

JWK map keys must be Strings. Encountered key '${key}' of type ${type}.

What it means

JWK member names must be JSON strings. During map-to-JWK conversion, applyFrom iterates map entries and throws IllegalArgumentException (via the explicit check) if any key is not a String, and asserts no key is null.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/security/JwkConverter.java:167

        }
        if (!(val instanceof String)) {
            String msg = "JWK " + param + " value must be a String. Type found: " + val.getClass().getName();
            throw new MalformedKeyException(msg);
        }
        String kty = (String) val;
        if (!Strings.hasText(kty)) {
            String msg = "JWK " + param + " value cannot be empty.";
            throw new MalformedKeyException(msg);
        }

        DynamicJwkBuilder<?, ?> builder = this.supplier.get();
        for (Map.Entry<?, ?> entry : map.entrySet()) {
            Object key = entry.getKey();
            Assert.notNull(key, "JWK map key cannot be null.");
            if (!(key instanceof String)) {
                String msg = "JWK map keys must be Strings. Encountered key '" + key + "' of type " +
                        key.getClass().getName() + ".";
                throw new IllegalArgumentException(msg);
            }
            String skey = (String) key;
            builder.add(skey, entry.getValue());
        }
        Jwk<?> jwk = builder.build();

        if (desiredType.isInstance(jwk)) {
            return desiredType.cast(jwk);
        }
        throw unexpectedIAE(jwk);
    }
}

View on GitHub (pinned to fb71496164)

Solutions

  1. Convert all map keys to Strings before building the JWK (e.g. enum.name(), String.valueOf(key))
  2. Fix upstream deserialization so JSON object keys stay Strings
  3. Use Properties.stringPropertyNames() or equivalent when copying from Properties

Example fix

// before
Map<Object,Object> m = props; // contains Integer keys
builder.build(m);
// after
Map<String,Object> m = props.entrySet().stream()
    .collect(Collectors.toMap(e -> String.valueOf(e.getKey()), Map.Entry::getValue));
builder.build(m);
Defensive patterns

Strategy: type-guard

Validate before calling

for (Object k : jwkMap.keySet()) { if (!(k instanceof String)) throw new IllegalArgumentException("All JWK keys must be Strings"); }

Type guard

boolean allStringKeys(Map<?,?> m) { return m != null && m.keySet().stream().allMatch(k -> k instanceof String); }

Try / catch

try { jwk = converter.applyFrom(map); } catch (IllegalArgumentException e) { log.error("Bad JWK map keys: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Passing a Map with non-String keys (e.g. Integer, enum instances) to JWK building/parsing APIs.

Common situations: Programmatically built maps using enum or numeric keys; converting from formats that allow non-string keys (e.g. YAML, Java Properties).

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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