FasterXML/jackson-databind · error · IllegalStateException

AnnotationIntrospector returned Class {}; expected Class<Con

Error message

AnnotationIntrospector returned Class {}; expected Class<Converter>

What it means

DatabindContext.converterInstance() received a Class object from the AnnotationIntrospector for a converter definition, but that Class does not implement the Converter interface. The library can only instantiate classes that are assignable to Converter, so it rejects the definition with the offending class name. This is a stricter, more specific sibling of the 'wrong type' error (index 0) and almost always points at an annotation or introspector pointing to the wrong class.

Source

Thrown at src/main/java/tools/jackson/databind/DatabindContext.java:470

            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 "
                    +converterDef.getClass().getName()+"; 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 "
                    +converterClass.getName()+"; expected Class<Converter>");
        }
        final MapperConfig<?> config = getConfig();
        HandlerInstantiator hi = config.getHandlerInstantiator();
        Converter<?,?> conv = (hi == null) ? null : hi.converterInstance(config, annotated, converterClass);
        if (conv == null) {
            conv = (Converter<?,?>) ClassUtil.createInstance(converterClass,
                    config.canOverrideAccessModifiers());
        }
        return (Converter<Object,Object>) conv;
    }

    /*
    /**********************************************************************
    /* Misc config access
    /**********************************************************************
     */

View on GitHub (pinned to a50c7d2a1d)

Solutions

  1. Open the named class and confirm whether it 'implements Converter'. If not, either make it implement Converter or move the reference to the correct annotation attribute (using= for ValueSerializer, contentUsing=, etc.).
  2. Check the @JsonSerialize/@JsonDeserialize annotation site cited in context for a wrong class reference.
  3. If a mixin is involved, verify the mixin's converter attribute points at a real Converter class.
  4. Recompile to catch the type mismatch at compile time where possible (use Class<? extends Converter<?,?>> typed fields in your own config holders).

Example fix

// before
@JsonSerialize(converter = MySerializer.class) // MySerializer extends ValueSerializer, NOT Converter
public Value getValue() { ... }
// after
@JsonSerialize(using = MySerializer.class)
public Value getValue() { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

// Before returning a converter Class, verify it implements Converter
static Class<?> checkedConverterClass(Class<?> c) {
    if (c != null && !Converter.class.isAssignableFrom(c)) {
        throw new IllegalStateException(c.getName() + " is not a Converter");
    }
    return c;
}

Type guard

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

Prevention

When it happens

Trigger: An annotation like @JsonSerialize(converter = SomeClass.class) or @JsonDeserialize(converter = SomeClass.class) where SomeClass is not a Converter; or a custom introspector returning a Class that is a serializer/deserializer, a factory, or an unrelated POJO instead of a Converter. Also triggered by mixins that remap the converter attribute to an arbitrary class.

Common situations: Confusing 'converter' with 'using' (which expects a ValueSerializer/ValueDeserializer class); copy-paste of a class reference after refactoring where the class lost its Converter implementation; 2.x-to-3.x migration where a class was renamed or its generics changed and no longer matches; using a @JsonSerialize.converter on a field whose target is a basic type with no custom Converter written.

Related errors


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