FasterXML/jackson-databind · error · IllegalStateException
AnnotationIntrospector returned Converter definition of type
Error message
AnnotationIntrospector returned Converter definition of type {}; expected type Converter or Class<Converter> instead What it means
Thrown by DatabindContext.converterInstance() when an AnnotationIntrospector returns a Converter definition object that is neither a Converter instance nor a Class. The @JsonSerialize/@JsonDeserialize converter attribute (or a custom AnnotationIntrospector) must resolve to one of those two types. If a third-party or custom introspector returns an arbitrary object (e.g., a String name, a Method, or a TypeReference), this error fires.
Solutions
- Ensure your AnnotationIntrospector returns either a Converter<?,?> instance or a Class<? extends Converter> from findSerializationConverter/findDeserializationConverter.
- If you use @JsonSerialize(converter=MyConverter.class), verify the annotation is on a class that actually implements Converter.
- Debug by logging the return value of your introspector's converter methods to confirm the runtime type.
Example fix
// before
@Override
public Object findSerializationConverter(Annotated a) {
return a.getName(); // wrong: returns a String
}
// after
@Override
public Object findSerializationConverter(Annotated a) {
return MyConverter.class; // returns Class<? extends Converter>
} Defensive patterns
Strategy: validation
Validate before calling
// Validate before returning from a custom introspector
Object result = myLogic();
if (result != null && !(result instanceof Converter<?,?>) && !(result instanceof Class)) {
throw new IllegalStateException("Converter def must be Converter or Class, got: " + result.getClass());
}
return result; Type guard
boolean isValidConverterDef(Object def) {
return def == null
|| def instanceof Converter<?,?>
|| (def instanceof Class<?> c && Converter.class.isAssignableFrom(c));
} Try / catch
try {
mapper.readValue(json, MyType.class);
} catch (IllegalStateException e) {
if (e.getMessage().contains("Converter definition of type")) {
// fix your AnnotationIntrospector return value
}
throw e;
} Prevention
- Always return Converter instances or Class<? extends Converter> from custom introspector converter methods.
- Write unit tests for custom AnnotationIntrospectors that assert the return type of converter methods.
- Avoid upgrading AnnotationIntrospector across Jackson major versions without checking method contract changes.
When it happens
Trigger: A custom AnnotationIntrospector overriding findSerializationConverter() or findDeserializationConverter() that returns a non-Converter, non-Class object. Or an annotation processor that misinterprets the converter annotation value and returns the annotation proxy itself instead of the Class.
Common situations: Upgrading a custom AnnotationIntrospector from Jackson 2.x to 3.x where method signatures changed. Integrating Kotlin/Scala modules whose introspectors have bugs. Returning an enum or annotation instance instead of the Class referenced by it.
Related errors
- AnnotationIntrospector returned Class
- AnnotationIntrospector.
- AnnotationIntrospector returned `Class<
- AnnotationIntrospector returned Class
- AnnotationIntrospector returned key deserializer definition…
AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11).
Data as JSON: /api/errors/f92b9694b08c20bb.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/tools/jackson/databind/DatabindContext.java:461
return resolver;
}
/**
* Helper method to use to construct a {@link Converter}, given a definition
* that may be either actual converter instance, or Class for instantiating one.
*/
@SuppressWarnings("unchecked")
public Converter<Object,Object> converterInstance(Annotated annotated,
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 "
+converterDef.getClass().getName()+"; 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 "
+converterClass.getName()+"; expected Class<Converter>");
}
final MapperConfig<?> config = getConfig();
HandlerInstantiator hi = config.getHandlerInstantiator();
Converter<?,?> conv = (hi == null) ? null : hi.converterInstance(config, annotated, converterClass);
if (conv == null) {
conv = (Converter<?,?>) ClassUtil.createInstance(converterClass,
config.canOverrideAccessModifiers());
}View on GitHub (pinned to 87876ca5c0)