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
- Return null, KeyDeserializer.None.class, a KeyDeserializer instance, or a Class<? extends KeyDeserializer> from findKeyDeserializer().
- If you need a class, ensure it is a Class object (not an instance) and that it extends KeyDeserializer (see error 42).
- 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
- From custom introspectors, return only null, KeyDeserializer.None.class, a KeyDeserializer, or a Class<? extends KeyDeserializer>.
- Keep key- and value-deserializer introspection methods separate; do not share return values.
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
- AnnotationIntrospector returned Class ${deserClass.getName()
- AnnotationIntrospector returned `Class<${deserClass.getName(
- AnnotationIntrospector returned Converter definition of type
- Failed to parse Date value '%s': %s
- Cannot pass null KeyDeserializers
AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11).
Data as JSON: /api/errors/4e917b1e01ae7326.
Report an issue: GitHub.