FasterXML/jackson-databind · error · IllegalStateException

AnnotationIntrospector returned Class

Error message

AnnotationIntrospector returned Class {}; expected Class<Converter>

What it means

Thrown by DatabindContext.converterInstance() when the object returned by AnnotationIntrospector is a Class, but that Class does not implement the Converter interface. The @JsonSerialize(converter=X.class) or @JsonDeserialize(converter=X.class) annotation requires X to be a valid Converter implementation.

Solutions

  1. Verify the class referenced in converter=... implements tools.jackson.databind.util.Converter.
  2. If you meant to use a custom serializer/deserializer, use the using= or contentUsing= attribute instead of converter=.
  3. Make the referenced class extend StdConverter<IN,OUT> for a zero-boilerplate Converter implementation.

Example fix

// before
@JsonSerialize(converter = MySerializer.class) // MySerializer extends ValueSerializer, not Converter
// after
@JsonSerialize(using = MySerializer.class) // correct attribute for a ValueSerializer
Defensive patterns

Strategy: validation

Validate before calling

// Verify a converter class before using it in an annotation
Class<?> converterClass = MyConverter.class;
if (!Converter.class.isAssignableFrom(converterClass)) {
    throw new IllegalArgumentException(converterClass + " does not implement Converter");
}

Type guard

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

Prevention

When it happens

Trigger: Annotating a property or type with @JsonSerialize(converter=SomeClass.class) or @JsonDeserialize(converter=SomeClass.class) where SomeClass does not implement tools.jackson.databind.util.Converter. Also triggered by a custom AnnotationIntrospector that returns a Class that is not a Converter subtype.

Common situations: Copy-paste errors where a serializer/deserializer class is accidentally used as a converter. Refactoring where a class stopped implementing Converter. Confusion between @JsonSerialize(using=...) and @JsonSerialize(converter=...).

Related errors


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

Appendix: 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 87876ca5c0)