FasterXML/jackson-databind · error · IllegalStateException
AnnotationIntrospector returned Class {}; expected Class<Key
Error message
AnnotationIntrospector returned Class {}; expected Class<KeyDeserializer> What it means
Thrown by DeserializationContextExt.keyDeserializerInstance when the introspector returns a Class for the key deserializer, but that Class does not extend KeyDeserializer. Only KeyDeserializer subclasses can be instantiated to deserialize map keys.
Source
Thrown at src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java:293
}
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());
}
}
// First: need to resolve
deser.resolve(this);
return deser;
}
/*
/**********************************************************************
/* Extended API, read methods
/**********************************************************************View on GitHub (pinned to a50c7d2a1d)
Solutions
- Make the referenced class extend KeyDeserializer and implement deserializeKey().
- If you meant a value deserializer, use @JsonDeserialize(using=...) instead.
- Verify the class hierarchy before wiring.
Example fix
// before
@JsonKeyDeserializer(ColorParser.class) // not a KeyDeserializer
class ColorParser { Color parse(String s){ ... } }
// after
class ColorKeyDeserializer extends KeyDeserializer {
@Override public Object deserializeKey(String key, DeserializationContext ctxt){ ... }
}
@JsonKeyDeserializer(ColorKeyDeserializer.class) Defensive patterns
Strategy: type-guard
Type guard
static boolean isKeyDeserializerClass(Class<?> c) {
return c != null && KeyDeserializer.class.isAssignableFrom(c);
} Prevention
- Verify @JsonKeyDeserializer targets extend KeyDeserializer.
- Don't confuse key deserializers (KeyDeserializer) with value deserializers (ValueDeserializer) — different base class.
- Add a smoke test that builds a mapper for each type using @JsonKeyDeserializer.
When it happens
Trigger: @JsonKeyDeserializer(MyClass.class) where MyClass does not extend KeyDeserializer, or a custom introspector returning a non-KeyDeserializer Class.
Common situations: Pointing @JsonKeyDeserializer at a regular value deserializer or utility class; a refactor that dropped the extends clause; confusing value and key deserializer types.
Related errors
- AnnotationIntrospector returned `Class<{}>`; expected `Class
- AnnotationIntrospector returned Class {}; expected Class<Val
- AnnotationIntrospector returned `Class<{}>`; expected `Class
- AnnotationIntrospector returned key deserializer definition
- AnnotationIntrospector returned Converter definition of type
AI-assisted analysis of FasterXML/jackson-databind@a50c7d2a1d (2026-08-06).
Data as JSON: /api/errors/7bd318a79ba120a8.
Report an issue: GitHub.