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 resolving a converter definition: the AnnotationIntrospector returned an object that is neither a Converter instance nor a Class. The introspector contract requires one of those two shapes (or null), so any other type is a bug in the introspector or a misbehaving annotation mapping.

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 a50c7d2a1d)

Solutions

  1. Audit the custom AnnotationIntrospector: every converter-returning method must yield null, a Converter<?,?> instance, or a Class<? extends Converter>.
  2. If resolving by name, look up the actual Class (Class.forName / registry) and return the raw Class, not a wrapper or String.
  3. Add a contract unit test asserting the introspector's converter method returns one of the legal shapes on every code path.

Example fix

// before
public class MyIntrospector extends AnnotationIntrospector {
    @Override public Object findDeserializationConverter(Annotated a) {
        return "com.example.MyConverter"; // String -> throws
    }
}
// after
public class MyIntrospector extends AnnotationIntrospector {
    @Override public Object findDeserializationConverter(Annotated a) {
        return MyConverter.class; // Class<? extends Converter>
    }
}
Defensive patterns

Strategy: type-guard

Type guard

static boolean isLegalConverterDef(Object def) {
    return def == null
        || def instanceof Converter<?,?>
        || (def instanceof Class<?> c && Converter.class.isAssignableFrom(c));
}

Prevention

When it happens

Trigger: A custom AnnotationIntrospector overriding findSerializationConverter/findDeserializationConverter (or equivalent) returns a String, a Type, a TypeReference, or some framework wrapper object instead of a Converter or Class<Converter>.

Common situations: A custom framework introspection layer that maps its own annotations to Jackson handlers but wraps refs in its own types; a buggy mixin/plugin intercepting converter resolution; returning a fully-qualified class name String instead of a Class.

Related errors


AI-assisted analysis of FasterXML/jackson-databind@a50c7d2a1d (2026-08-06). Data as JSON: /api/errors/371d132f044e1f94. Report an issue: GitHub.