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 by BasicDeserializerFactory._valueInstantiatorInstance when an AnnotationIntrospector returned a value-instantiator definition whose runtime type is neither a ValueInstantiator instance nor a Class. NOTE: the message text in the source says 'key deserializer definition' / 'KeyDeserializer', but the code path actually handles @JsonValueInstantiator / ValueInstantiator resolution -- the wording is misleading; the trigger is a malformed ValueInstantiator definition returned by a custom introspector.

Solutions

  1. Fix the custom AnnotationIntrospector to return a ValueInstantiator instance or a Class<? extends ValueInstantiator> for the value-instantiator slot (or null).
  2. Check @JsonValueInstantiator on the target type points to a Class<ValueInstantiator> and is not being rewritten by a mixin/introspector.
  3. If you do not need a custom instantiator, remove the annotation/introspector override.

Example fix

// before: introspector returns a String for the instantiator slot
@Override
public Object findValueInstantiator(Annotated a) {
    return a.getName();
}

// after
@Override
public Object findValueInstantiator(Annotated a) {
    return MyValueInstantiator.class;
}
Defensive patterns

Strategy: type-guard

Validate before calling

Object def = introspector.findValueInstantiator(annotated);
if (def != null && !(def instanceof ValueInstantiator) && !(def instanceof Class)) {
    throw new IllegalStateException("Bad value-instantiator definition: " + def.getClass());
}

Type guard

boolean isInstantiatorDef(Object d) {
    return d == null || d instanceof ValueInstantiator || d instanceof Class;
}

Prevention

When it happens

Trigger: A custom AnnotationIntrospector returning an arbitrary non-Class object from the value-instantiator lookup (used for @JsonValueInstantiator), or annotation data that resolves to something other than a ValueInstantiator or Class.

Common situations: Custom introspector implementation returning the wrong object type for the value-instantiator slot; a plugin/framework remapping annotations incorrectly; copying an introspector pattern from a key-deserializer hook into the value-instantiator hook.

Related errors


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

Appendix: 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 87876ca5c0)