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 DeserializationContextExt.keyDeserializerInstance when the object returned for a key-deserializer definition is neither a KeyDeserializer instance nor a Class. The introspector must yield one of those two shapes (or null); anything else violates the contract.
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 a50c7d2a1d)
Solutions
- Audit the introspector's key-deserializer-returning methods: return null, a KeyDeserializer instance, or a Class<? extends KeyDeserializer>.
- Resolve any name-based lookup to the actual Class before returning.
- Add a contract unit test for the introspector method.
Example fix
// before
public Object findKeyDeserializer(Annotated a) {
return "colorKeyDeser"; // String -> throws
}
// after
public Object findKeyDeserializer(Annotated a) {
return ColorKeyDeserializer.class; // Class<? extends KeyDeserializer>
} Defensive patterns
Strategy: type-guard
Type guard
static boolean isLegalKeyDeserializerDef(Object def) {
return def == null
|| def instanceof KeyDeserializer
|| (def instanceof Class<?> c && KeyDeserializer.class.isAssignableFrom(c));
} Prevention
- Key-deserializer introspector methods return null|instance|Class.
- Keep key and value deserializer resolution distinct in custom introspection code.
- Unit-test introspector methods returning key deserializers against the contract.
When it happens
Trigger: A custom AnnotationIntrospector.findKeyDeserializer (or custom @JsonKeyDeserializer handling) returns a non-Class, non-KeyDeserializer object (a String, a Type, a wrapper).
Common situations: A custom introspection layer wrapping key-deserializer refs in its own types; an annotation pointing at a name/Type instead of a class; a library upgrade tightening the contract.
Related errors
- AnnotationIntrospector returned key deserializer definition
- AnnotationIntrospector returned deserializer definition of t
- AnnotationIntrospector returned Class {}; expected Class<Key
- Multiple suitable annotated Creator factory methods to be us
- AnnotationIntrospector returned Converter definition of type
AI-assisted analysis of FasterXML/jackson-databind@a50c7d2a1d (2026-08-06).
Data as JSON: /api/errors/0124beba6690259c.
Report an issue: GitHub.