FasterXML/jackson-databind · error · IllegalStateException
`AnnotationIntrospector` returned `Converter` definition of…
Error message
`AnnotationIntrospector` returned `Converter` definition of type {}; expected type `Converter` or `Class<Converter>` instead What it means
Thrown by MapperConfigBase._createConverter when an AnnotationIntrospector returned a Converter definition whose runtime type is neither a Converter instance nor a Class. Jackson expects @JsonSerialize(converter=...)/@JsonDeserialize(converter=...) (or the introspector equivalents) to resolve to a Converter or a Class<Converter>; anything else is treated as an introspector bug.
Solutions
- Fix the custom AnnotationIntrospector to return a Converter instance or a Class<? extends Converter> (or Converter.None marker, or null).
- If using @JsonDeserialize(converter=X.class)/@JsonSerialize(converter=X.class), ensure the annotation value is a Converter class and not being remapped by a mixin/introspector.
- Remove or correct the introspector override that is rewriting converter definitions.
Example fix
// before
@Override
public Object findDeserializationConverter(Annotated a) {
return a.getName(); // wrong type: String
}
// after
@Override
public Object findDeserializationConverter(Annotated a) {
return MyConverter.class; // Class<? extends Converter>
} Defensive patterns
Strategy: type-guard
Validate before calling
Object def = introspector.findDeserializationConverter(annotated);
if (def != null && !(def instanceof Converter) && !(def instanceof Class)) {
throw new IllegalStateException("Bad converter definition: " + def.getClass());
} Type guard
boolean isConverterDef(Object d) {
return d == null || d instanceof Converter<?,?> || d instanceof Class;
} Try / catch
try {
mapper.readValue(json, Target.class);
} catch (IllegalStateException e) {
if (e.getMessage().contains("Converter definition of type")) {
// fix the introspector to return Converter or Class
}
} Prevention
- Custom introspectors must return Converter, Class<? extends Converter>, Converter.None, or null from converter hooks.
- Add unit tests for introspector return types on every annotated element.
- Avoid remapping annotations to internal descriptor objects.
When it happens
Trigger: A custom AnnotationIntrospector's findDeserializationConverter / findSerializationConverter returns an arbitrary object (e.g. a String, a Type, a Method) instead of a Converter or Class. Also possible via a faulty mixin or annotation-based plugin.
Common situations: Writing a custom introspector and returning the wrong field by mistake; a framework that wraps annotations and leaks an internal type; upgrading Jackson where the introspector contract was tightened.
Related errors
- AnnotationIntrospector returned `Class<
- 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/371d132f044e1f94.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/tools/jackson/databind/cfg/MapperConfigBase.java:739
/*
/**********************************************************************
/* Helper methods for implementations
/**********************************************************************
*/
@SuppressWarnings("unchecked")
protected Converter<Object,Object> _createConverter(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 "
+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)