FasterXML/jackson-databind · error · IllegalStateException
AnnotationIntrospector returned `Class<
Error message
AnnotationIntrospector returned `Class<{}>`; expected `Class<Converter>` What it means
Thrown by MapperConfigBase._createConverter when an AnnotationIntrospector returned a Class that does not implement Converter. The converter slot (@JsonDeserialize(converter=...), @JsonSerialize(converter=...), or introspector) must point to a Converter implementation; a class that is not assignable to Converter is rejected at converter-creation time.
Solutions
- Make the referenced class implement Converter<SourceType, TargetType> (implement convert and getInputType/getOutputType).
- Point the converter annotation at the correct class that already implements Converter.
- If you intended a two-step conversion, wrap it in a proper Converter instead of a Function.
Example fix
// before
@JsonDeserialize(converter = StringProcessor.class) // not a Converter
public class StringProcessor { String apply(String s){...} }
// after
@JsonDeserialize(converter = StringProcessor.class)
public class StringProcessor implements Converter<String, Value> {
@Override public Value convert(String s) { ... }
@Override public JavaType getInputType(TypeFactory f) { ... }
@Override public JavaType getOutputType(TypeFactory f) { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
Class<?> cls = (Class<?>) introspector.findDeserializationConverter(annotated);
if (cls != null && !Converter.class.isAssignableFrom(cls)) {
throw new IllegalStateException(cls + " is not a Converter");
} Type guard
boolean isConverterClass(Class<?> c) {
return c != null && Converter.class.isAssignableFrom(c);
} Prevention
- Ensure every converter class referenced in annotations implements Converter.
- Implement getInputType/getOutputType as well as convert.
- Run a mapper build with the annotated type in tests to fail fast.
When it happens
Trigger: Pointing @JsonSerialize/@JsonDeserialize converter to a Class that does not implement tools.jackson.databind.util.Converter, or a custom introspector returning such a class.
Common situations: Using a plain Function/lambda class as a converter by mistake; refactoring a class so it no longer implements Converter but keeping the annotation; wrong class referenced in the annotation.
Related errors
- `AnnotationIntrospector` returned `Converter` definition of…
- AnnotationIntrospector returned Class
- AnnotationIntrospector returned deserializer definition of…
- AnnotationIntrospector returned key deserializer definition…
- AnnotationIntrospector returned Class
AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11).
Data as JSON: /api/errors/7a686bb919f747d0.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/tools/jackson/databind/cfg/MapperConfigBase.java:748
Object converterDef)
{
if (converterDef == null) {
return null;
}
if (converterDef instanceof Converter<?,?>) {
return (Converter<Object,Object>) converterDef;
}
if (!(converterDef instanceof Class)) {
throw new IllegalStateException("`AnnotationIntrospector` returned `Converter` definition of type "
+ClassUtil.classNameOf(converterDef)+"; expected type `Converter` or `Class<Converter>` instead");
}
Class<?> converterClass = (Class<?>)converterDef;
// there are some known "no class" markers to consider too:
if (converterClass == Converter.None.class || ClassUtil.isBogusClass(converterClass)) {
return null;
}
if (!Converter.class.isAssignableFrom(converterClass)) {
throw new IllegalStateException("AnnotationIntrospector returned `Class<"
+ClassUtil.classNameOf(converterClass)+"`>; expected `Class<Converter>`");
}
HandlerInstantiator hi = getHandlerInstantiator();
Converter<?,?> conv = (hi == null) ? null : hi.converterInstance(this, annotated, converterClass);
if (conv == null) {
conv = (Converter<?,?>) ClassUtil.createInstance(converterClass,
canOverrideAccessModifiers());
}
return (Converter<Object,Object>) conv;
}
}
View on GitHub (pinned to 87876ca5c0)