FasterXML/jackson-databind · error · IllegalStateException

AnnotationIntrospector returned key deserializer definition

Error message

AnnotationIntrospector returned key deserializer definition of type ${deserDef.getClass().getName()}; expected type KeyDeserializer or Class<KeyDeserializer> instead

What it means

Thrown by keyDeserializerInstance when the object returned as a key-deserializer definition is neither a KeyDeserializer instance nor a Class. Jackson only accepts those two shapes for a key deserializer definition; anything else (a String, an arbitrary bean, a TypeReference) is rejected.

Source

Thrown at src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java:283

        // First: need to resolve
        deser.resolve(this);
        return (ValueDeserializer<Object>) deser;
    }

    @Override
    public final KeyDeserializer keyDeserializerInstance(Annotated ann, Object deserDef)
    {
        if (deserDef == null) {
            return null;
        }

        KeyDeserializer deser;

        if (deserDef instanceof KeyDeserializer keyDeserializer) {
            deser = keyDeserializer;
        } else {
            if (!(deserDef instanceof Class)) {
                throw new IllegalStateException("AnnotationIntrospector returned key deserializer definition of type "
                        +deserDef.getClass().getName()
                        +"; expected type KeyDeserializer or Class<KeyDeserializer> instead");
            }
            Class<?> deserClass = (Class<?>)deserDef;
            // there are some known "no class" markers to consider too:
            if (deserClass == KeyDeserializer.None.class || ClassUtil.isBogusClass(deserClass)) {
                return null;
            }
            if (!KeyDeserializer.class.isAssignableFrom(deserClass)) {
                throw new IllegalStateException("AnnotationIntrospector returned Class "+deserClass.getName()
                        +"; expected Class<KeyDeserializer>");
            }
            HandlerInstantiator hi = _config.getHandlerInstantiator();
            deser = (hi == null) ? null : hi.keyDeserializerInstance(_config, ann, deserClass);
            if (deser == null) {
                deser = (KeyDeserializer) ClassUtil.createInstance(deserClass,
                        _config.canOverrideAccessModifiers());
            }

View on GitHub (pinned to 87876ca5c0)

Solutions

  1. Return null, KeyDeserializer.None.class, a KeyDeserializer instance, or a Class<? extends KeyDeserializer> from findKeyDeserializer().
  2. If you need a class, ensure it is a Class object (not an instance) and that it extends KeyDeserializer (see error 42).
  3. Double-check that the introspector method override is for keys, not values.

Example fix

// before
@Override
public Object findKeyDeserializer(Annotated a) {
    return new MyValueDeserializer(); // wrong type
}

// after
@Override
public Object findKeyDeserializer(Annotated a) {
    return new MyKeyDeserializer(); // extends KeyDeserializer
}
Defensive patterns

Strategy: type-guard

Validate before calling

Object def = introspector.findKeyDeserializer(annotated);
if (def != null && !(def instanceof KeyDeserializer) && !(def instanceof Class)) {
    throw new IllegalStateException("Bad key deserializer def: " + def.getClass());
}

Type guard

static boolean isAcceptableKeyDeserDef(Object def) {
    return def == null || def instanceof KeyDeserializer || def instanceof Class;
}

Try / catch

try { mapper.readValue(json, type); }
catch (IllegalStateException e) {
    if (e.getMessage().contains("expected type KeyDeserializer or Class<KeyDeserializer>")) {
        // introspector returned wrong shape; fix findKeyDeserializer
    } else throw e;
}

Prevention

When it happens

Trigger: An AnnotationIntrospector.findKeyDeserializer() override returning an object that is not a KeyDeserializer and not a Class; a custom module/key-deserializer registration passing the wrong object type; using @JsonKeyDeserializer with a value that is neither a class nor an instance.

Common situations: Returning the value deserializer instance where the key deserializer was expected; returning a Class<ValueDeserializer> instead of Class<KeyDeserializer>; a buggy custom introspector returning the raw annotation or a configuration object.

Related errors


AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11). Data as JSON: /api/errors/4e917b1e01ae7326. Report an issue: GitHub.