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

  1. Fix the custom AnnotationIntrospector to return a Converter instance or a Class<? extends Converter> (or Converter.None marker, or null).
  2. 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.
  3. 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

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


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)