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

  1. Make the referenced class implement Converter<SourceType, TargetType> (implement convert and getInputType/getOutputType).
  2. Point the converter annotation at the correct class that already implements Converter.
  3. 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

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


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)