FasterXML/jackson-databind · error · IllegalStateException

AnnotationIntrospector returned Converter definition of type

Error message

AnnotationIntrospector returned Converter definition of type {}; expected type Converter or Class<Converter> instead

What it means

Thrown by DatabindContext.converterInstance() when an AnnotationIntrospector returns a Converter definition object that is neither a Converter instance nor a Class. The @JsonSerialize/@JsonDeserialize converter attribute (or a custom AnnotationIntrospector) must resolve to one of those two types. If a third-party or custom introspector returns an arbitrary object (e.g., a String name, a Method, or a TypeReference), this error fires.

Solutions

  1. Ensure your AnnotationIntrospector returns either a Converter<?,?> instance or a Class<? extends Converter> from findSerializationConverter/findDeserializationConverter.
  2. If you use @JsonSerialize(converter=MyConverter.class), verify the annotation is on a class that actually implements Converter.
  3. Debug by logging the return value of your introspector's converter methods to confirm the runtime type.

Example fix

// before
@Override
public Object findSerializationConverter(Annotated a) {
    return a.getName(); // wrong: returns a String
}
// after
@Override
public Object findSerializationConverter(Annotated a) {
    return MyConverter.class; // returns Class<? extends Converter>
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate before returning from a custom introspector
Object result = myLogic();
if (result != null && !(result instanceof Converter<?,?>) && !(result instanceof Class)) {
    throw new IllegalStateException("Converter def must be Converter or Class, got: " + result.getClass());
}
return result;

Type guard

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

Try / catch

try {
    mapper.readValue(json, MyType.class);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("Converter definition of type")) {
        // fix your AnnotationIntrospector return value
    }
    throw e;
}

Prevention

When it happens

Trigger: A custom AnnotationIntrospector overriding findSerializationConverter() or findDeserializationConverter() that returns a non-Converter, non-Class object. Or an annotation processor that misinterprets the converter annotation value and returns the annotation proxy itself instead of the Class.

Common situations: Upgrading a custom AnnotationIntrospector from Jackson 2.x to 3.x where method signatures changed. Integrating Kotlin/Scala modules whose introspectors have bugs. Returning an enum or annotation instance instead of the Class referenced by it.

Related errors


AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11). Data as JSON: /api/errors/f92b9694b08c20bb. Report an issue: GitHub.

Appendix: source

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

        return resolver;
    }

    /**
     * Helper method to use to construct a {@link Converter}, given a definition
     * that may be either actual converter instance, or Class for instantiating one.
     */
    @SuppressWarnings("unchecked")
    public Converter<Object,Object> converterInstance(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 "
                    +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());
        }

View on GitHub (pinned to 87876ca5c0)