FasterXML/jackson-databind · error · IllegalStateException
AnnotationIntrospector returned deserializer definition of…
Error message
AnnotationIntrospector returned deserializer definition of type ${deserDef.getClass().getName()}; expected type `ValueDeserializer` or `Class<ValueDeserializer>` instead What it means
Thrown by DeserializationContextExt.deserializerInstance when an AnnotationIntrospector returned a deserializer definition whose runtime type is neither a ValueDeserializer instance nor a Class. Jackson expects @JsonDeserialize(using=...) (or the introspector equivalent) to resolve to a ValueDeserializer or a Class<ValueDeserializer>; anything else is treated as an introspector/annotation bug.
Solutions
- Fix the custom AnnotationIntrospector so findDeserializer returns a ValueDeserializer instance or a Class<? extends ValueDeserializer> (or ValueDeserializer.None, or null).
- Ensure @JsonDeserialize(using=X.class) points to a ValueDeserializer subclass and is not being rewritten by a mixin/introspector.
- Remove the introspector override that is rewriting deserializer definitions if it is incorrect.
Example fix
// before: introspector returns a String
@Override
public Object findDeserializer(Annotated a) {
return a.getName();
}
// after: returns a Class<? extends ValueDeserializer>
@Override
public Object findDeserializer(Annotated a) {
return MyValueDeserializer.class;
} Defensive patterns
Strategy: type-guard
Validate before calling
Object def = introspector.findDeserializer(annotated);
if (def != null && !(def instanceof ValueDeserializer) && !(def instanceof Class)) {
throw new IllegalStateException("Bad deserializer definition: " + def.getClass());
} Type guard
boolean isDeserializerDef(Object d) {
return d == null || d instanceof ValueDeserializer<?> || d instanceof Class;
} Prevention
- Custom introspectors must return ValueDeserializer, Class<? extends ValueDeserializer>, ValueDeserializer.None, or null.
- Ensure @JsonDeserialize(using=X.class) always names a ValueDeserializer subclass.
- Add unit tests for introspector return values on every annotated element.
When it happens
Trigger: A custom AnnotationIntrospector's findDeserializer returns an arbitrary object (a String, Type, bean, etc.) instead of a ValueDeserializer or Class; a framework/annotation remapper leaking the wrong object into the deserializer slot.
Common situations: Writing a custom introspector and returning the wrong field; an annotation-driven framework that wraps @JsonDeserialize and returns a descriptor object instead of a class; upgrading Jackson where the introspector contract was tightened to disallow non-Class/non-ValueDeserializer returns.
Related errors
- AnnotationIntrospector returned `Class<
- AnnotationIntrospector returned Class
- `AnnotationIntrospector` returned `Converter` definition of…
- AnnotationIntrospector returned key deserializer definition…
- _anySetter already set to non-null
AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11).
Data as JSON: /api/errors/cad2ba2df6a527c4.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java:246
/**********************************************************************
*/
@SuppressWarnings("unchecked")
@Override
public ValueDeserializer<Object> deserializerInstance(Annotated ann, Object deserDef)
{
if (deserDef == null) {
return null;
}
ValueDeserializer<?> deser;
if (deserDef instanceof ValueDeserializer valueDeserializer) {
deser = valueDeserializer;
} else {
// Alas, there's no way to force return type of "either class
// X or Y" -- need to throw an exception after the fact
if (!(deserDef instanceof Class)) {
throw new IllegalStateException("AnnotationIntrospector returned deserializer definition of type "
+deserDef.getClass().getName()
+"; expected type `ValueDeserializer` or `Class<ValueDeserializer>` instead");
}
Class<?> deserClass = (Class<?>)deserDef;
// there are some known "no class" markers to consider too:
if (deserClass == ValueDeserializer.None.class || ClassUtil.isBogusClass(deserClass)) {
return null;
}
if (!ValueDeserializer.class.isAssignableFrom(deserClass)) {
throw new IllegalStateException("AnnotationIntrospector returned `Class<"+deserClass.getName()+">`; expected `Class<ValueDeserializer>`");
}
HandlerInstantiator hi = _config.getHandlerInstantiator();
deser = (hi == null) ? null : hi.deserializerInstance(_config, ann, deserClass);
if (deser == null) {
deser = (ValueDeserializer<?>) ClassUtil.createInstance(deserClass,
_config.canOverrideAccessModifiers());
}
}View on GitHub (pinned to 87876ca5c0)