jwtk/jjwt · error · io.jsonwebtoken.security.MalformedKeyException

JWK kty value must be a String. Type found: ${type}

Error message

JWK kty value must be a String. Type found: ${type}

What it means

Thrown by JwkConverter.applyFrom when the kty (key type) member of a JWK JSON object is present but is not a JSON string. RFC 7517 §4.1 requires kty to be a case-sensitive string (e.g. "oct", "RSA", "EC", "OKP"), so a non-String value marks the JWK as malformed and conversion fails with MalformedKeyException.

Source

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

            throw new IllegalArgumentException(msg);
        }
        final Map<?, ?> map = Collections.immutable((Map<?, ?>) o);

        Parameter<String> param = AbstractJwk.KTY;
        // mandatory for all JWKs: https://datatracker.ietf.org/doc/html/rfc7517#section-4.1
        // no need for builder param type conversion overhead if this isn't present:
        if (Collections.isEmpty(map) || !map.containsKey(param.getId())) {
            String msg = "JWK is missing required " + param + " parameter.";
            throw new MalformedKeyException(msg);
        }
        Object val = map.get(param.getId());
        if (val == null) {
            String msg = "JWK " + param + " value cannot be null.";
            throw new MalformedKeyException(msg);
        }
        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());

View on GitHub (pinned to fb71496164)

Solutions

  1. Set kty as a plain String ("RSA", "EC", "oct", "OKP")
  2. If using an enum, pass enum.name() or toString() instead of the enum instance
  3. Fix custom deserializers that map kty to non-String types

Example fix

// before
Map<String,Object> jwk = Map.of("kty", KeyType.RSA, "n", n, "e", e);
// after
Map<String,Object> jwk = Map.of("kty", KeyType.RSA.name(), "n", n, "e", e);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(jwkMap.get("kty") instanceof String)) { throw new IllegalArgumentException("kty must be a String"); }

Type guard

String ktyOrNull(Map<String,?> m) { Object v = m == null ? null : m.get("kty"); return v instanceof String s ? s : null; }

Try / catch

try { jwk = Jwks.builder().build(); } catch (MalformedKeyException e) { log.error("kty must be a String: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Passing a map where kty is a non-String object (e.g. a Map, List, enum object, or number) into JWK conversion.

Common situations: Programmatically constructed maps where kty was set to a custom key-type enum instead of its String value; custom deserializers producing non-String values.

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