FasterXML/jackson-databind · error · IllegalStateException

AnnotationIntrospector returned `Class<{}>`; expected `Class

Error message

AnnotationIntrospector returned `Class<{}>`; expected `Class<Converter>`

What it means

Thrown by MapperConfigBase._createConverter when the introspector returns a Class for a converter, but that Class does not implement Converter. Jackson can only instantiate converter classes that satisfy the Converter interface, so any other class is rejected.

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

Solutions

  1. Make the referenced class implement Converter<?,?> (override convert, getInputType, getOutputType).
  2. If you intended a different conversion mechanism, use a custom ValueDeserializer/ValueSerializer instead of a Converter.
  3. Verify the class referenced in @JsonSerialize(converter=...) / @JsonDeserialize(converter=...) actually implements Converter.

Example fix

// before
@JsonDeserialize(converter = StringReverser.class) // does NOT implement Converter
class StringReverser { String apply(String s){ return new StringBuilder(s).reverse().toString(); } }
// after
class StringReverser implements Converter<String,String> {
    public String convert(String s){ return new StringBuilder(s).reverse().toString(); }
    public JavaType getInputType(TypeFactory f){ return f.constructType(String.class); }
    public JavaType getOutputType(TypeFactory f){ return f.constructType(String.class); }
}
Defensive patterns

Strategy: type-guard

Type guard

static boolean isConverterClass(Class<?> c) {
    return c != null && Converter.class.isAssignableFrom(c);
}

Prevention

When it happens

Trigger: An introspector (or @JsonDeserialize(converter=...) / @JsonSerialize(converter=...)) returns a Class that is not assignable to Converter<?,?> — e.g. a Function, a Supplier, a builder, or an unrelated bean.

Common situations: Pointing the converter annotation at the wrong class (a builder, a formatter, a DTO); a refactor that renamed or moved the converter without updating the annotation; confusing a Converter with a ValueSerializer/ValueDeserializer.

Related errors


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