FasterXML/jackson-databind · error · IllegalStateException

AnnotationIntrospector returned key deserializer definition

Error message

AnnotationIntrospector returned key deserializer definition of type {}; expected type KeyDeserializer or Class<KeyDeserializer> instead

What it means

Thrown from BasicDeserializerFactory._valueInstantiatorInstance when the object returned for a ValueInstantiator definition is neither a ValueInstantiator instance nor a Class. IMPORTANT: the message text says 'key deserializer definition' but the code path enforces the VALUE-INSTANTIATOR contract — the wording is a copy-paste bug; the actual fault is in value-instantiator resolution (e.g. via @JsonValueInstantiator or a custom introspector).

Source

Thrown at src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java:285

        }

        return creators.constructValueInstantiator(ctxt);
    }

    public ValueInstantiator _valueInstantiatorInstance(DeserializationConfig config,
            Annotated annotated, Object instDef)
    {
        if (instDef == null) {
            return null;
        }

        ValueInstantiator inst;

        if (instDef instanceof ValueInstantiator valueInstantiator) {
            return valueInstantiator;
        }
        if (!(instDef instanceof Class)) {
            throw new IllegalStateException("AnnotationIntrospector returned key deserializer definition of type "
                    +instDef.getClass().getName()
                    +"; expected type KeyDeserializer or Class<KeyDeserializer> instead");
        }
        Class<?> instClass = (Class<?>)instDef;
        if (ClassUtil.isBogusClass(instClass)) {
            return null;
        }
        if (!ValueInstantiator.class.isAssignableFrom(instClass)) {
            throw new IllegalStateException("AnnotationIntrospector returned Class "+instClass.getName()
                    +"; expected Class<ValueInstantiator>");
        }
        HandlerInstantiator hi = config.getHandlerInstantiator();
        if (hi != null) {
            inst = hi.valueInstantiatorInstance(config, annotated, instClass);
            if (inst != null) {
                return inst;
            }
        }

View on GitHub (pinned to a50c7d2a1d)

Solutions

  1. Audit the introspector: the value-instantiator resolution must return null, a ValueInstantiator instance, or a Class<? extends ValueInstantiator>.
  2. Do NOT trust the 'key deserializer' wording in the message — it is misleading; investigate the ValueInstantiator/@JsonValueInstantiator path first.
  3. If resolving by name, resolve to the raw Class before returning; never return a Type or TypeReference.

Example fix

// before: introspector returns a TypeReference wrapper instead of a Class
public Object findValueInstantiator(Annotated a) {
    return new TypeReference<MyInstantiator>(){}; // not a Class -> throws (with misleading msg)
}
// after
public Object findValueInstantiator(Annotated a) {
    return MyInstantiator.class; // raw Class<? extends ValueInstantiator>
}
Defensive patterns

Strategy: type-guard

Type guard

static boolean isLegalInstantiatorDef(Object def) {
    return def == null
        || def instanceof ValueInstantiator
        || (def instanceof Class<?> c && ValueInstantiator.class.isAssignableFrom(c));
}

Prevention

When it happens

Trigger: A custom AnnotationIntrospector's value-instantiator resolution returns an unexpected type — not a ValueInstantiator, not a Class (e.g. a Type, a TypeReference, a String, or a framework wrapper). Reachable through @JsonCreator/@JsonValueInstantiator handling or a misbehaving HandlerInstantiator.

Common situations: A custom introspection framework intercepting value-instantiator resolution and returning its own wrapper type; an annotation that resolves to a Type instead of a Class; a library upgrade where the introspector method signature/contract changed.

Related errors


AI-assisted analysis of FasterXML/jackson-databind@a50c7d2a1d (2026-08-06). Data as JSON: /api/errors/786c30b67d4a1687. Report an issue: GitHub.