FasterXML/jackson-databind · error · IllegalStateException
AnnotationIntrospector returned `Class<{}>`; expected `Class
Error message
AnnotationIntrospector returned `Class<{}>`; expected `Class<ValueDeserializer>` What it means
Thrown by DeserializationContextExt.deserializerInstance when the introspector returns a Class for the deserializer, but that Class does not extend ValueDeserializer. Only ValueDeserializer subclasses can be instantiated to drive deserialization.
Source
Thrown at src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java:256
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());
}
}
// 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;View on GitHub (pinned to a50c7d2a1d)
Solutions
- Make the referenced class extend ValueDeserializer<T> and implement deserialize().
- If you meant a serializer, use @JsonSerialize(using=...) instead.
- Verify the class referenced in @JsonDeserialize(using=...).
Example fix
// before
@JsonDeserialize(using = ItemFormatter.class) // not a ValueDeserializer
class ItemFormatter { String format(Item i){ ... } }
// after
class ItemDeserializer extends ValueDeserializer<Item> {
@Override public Item deserialize(JsonParser p, DeserializationContext c){ ... }
}
@JsonDeserialize(using = ItemDeserializer.class) Defensive patterns
Strategy: type-guard
Type guard
static boolean isDeserializerClass(Class<?> c) {
return c != null && ValueDeserializer.class.isAssignableFrom(c);
} Prevention
- Verify @JsonDeserialize(using=...) targets extend ValueDeserializer.
- Don't confuse @JsonDeserialize(using=) (deserializer) with @JsonSerialize(using=) (serializer) or @JsonDeserialize(converter=).
- Keep serializers and deserializers in distinctly named packages.
When it happens
Trigger: @JsonDeserialize(using = MyClass.class) where MyClass does not extend ValueDeserializer, or a custom introspector returning a non-deserializer Class.
Common situations: Annotating a serializer, a converter, or a plain utility class with using= instead of a real deserializer; a refactor that dropped the extends ValueDeserializer clause; pointing at an interface that isn't a deserializer.
Related errors
- AnnotationIntrospector returned `Class<{}>`; expected `Class
- AnnotationIntrospector returned Class {}; expected Class<Val
- AnnotationIntrospector returned deserializer definition of t
- AnnotationIntrospector returned Class {}; expected Class<Key
- AnnotationIntrospector returned Converter definition of type
AI-assisted analysis of FasterXML/jackson-databind@a50c7d2a1d (2026-08-06).
Data as JSON: /api/errors/128b9a93c47d984c.
Report an issue: GitHub.