{"record":{"id":"f92b9694b08c20bb","repo":"FasterXML/jackson-databind","slug":"annotationintrospector-returned-converter-definiti","errorCode":null,"errorMessage":"AnnotationIntrospector returned Converter definition of type {}; expected type Converter or Class<Converter> instead","messagePattern":"AnnotationIntrospector returned Converter definition of type (.+?); expected type Converter or Class<Converter> instead","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/tools/jackson/databind/DatabindContext.java","lineNumber":461,"sourceCode":"        return resolver;\n    }\n\n    /**\n     * Helper method to use to construct a {@link Converter}, given a definition\n     * that may be either actual converter instance, or Class for instantiating one.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public Converter<Object,Object> converterInstance(Annotated annotated,\n            Object converterDef)\n    {\n        if (converterDef == null) {\n            return null;\n        }\n        if (converterDef instanceof Converter<?,?>) {\n            return (Converter<Object,Object>) converterDef;\n        }\n        if (!(converterDef instanceof Class)) {\n            throw new IllegalStateException(\"AnnotationIntrospector returned Converter definition of type \"\n                    +converterDef.getClass().getName()+\"; expected type Converter or Class<Converter> instead\");\n        }\n        Class<?> converterClass = (Class<?>)converterDef;\n        // there are some known \"no class\" markers to consider too:\n        if (converterClass == Converter.None.class || ClassUtil.isBogusClass(converterClass)) {\n            return null;\n        }\n        if (!Converter.class.isAssignableFrom(converterClass)) {\n            throw new IllegalStateException(\"AnnotationIntrospector returned Class \"\n                    +converterClass.getName()+\"; expected Class<Converter>\");\n        }\n        final MapperConfig<?> config = getConfig();\n        HandlerInstantiator hi = config.getHandlerInstantiator();\n        Converter<?,?> conv = (hi == null) ? null : hi.converterInstance(config, annotated, converterClass);\n        if (conv == null) {\n            conv = (Converter<?,?>) ClassUtil.createInstance(converterClass,\n                    config.canOverrideAccessModifiers());\n        }","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/FasterXML/jackson-databind/blob/87876ca5c0569b4933aec2d30d6225e4b9ba3a43/src/main/java/tools/jackson/databind/DatabindContext.java#L443-L479","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure your AnnotationIntrospector returns either a Converter<?,?> instance or a Class<? extends Converter> from findSerializationConverter/findDeserializationConverter.","If you use @JsonSerialize(converter=MyConverter.class), verify the annotation is on a class that actually implements Converter.","Debug by logging the return value of your introspector's converter methods to confirm the runtime type."],"exampleFix":"// before\n@Override\npublic Object findSerializationConverter(Annotated a) {\n    return a.getName(); // wrong: returns a String\n}\n// after\n@Override\npublic Object findSerializationConverter(Annotated a) {\n    return MyConverter.class; // returns Class<? extends Converter>\n}","handlingStrategy":"validation","validationCode":"// Validate before returning from a custom introspector\nObject result = myLogic();\nif (result != null && !(result instanceof Converter<?,?>) && !(result instanceof Class)) {\n    throw new IllegalStateException(\"Converter def must be Converter or Class, got: \" + result.getClass());\n}\nreturn result;","typeGuard":"boolean isValidConverterDef(Object def) {\n    return def == null\n        || def instanceof Converter<?,?>\n        || (def instanceof Class<?> c && Converter.class.isAssignableFrom(c));\n}","tryCatchPattern":"try {\n    mapper.readValue(json, MyType.class);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"Converter definition of type\")) {\n        // fix your AnnotationIntrospector return value\n    }\n    throw e;\n}","preventionTips":["Always return Converter instances or Class<? extends Converter> from custom introspector converter methods.","Write unit tests for custom AnnotationIntrospectors that assert the return type of converter methods.","Avoid upgrading AnnotationIntrospector across Jackson major versions without checking method contract changes."],"tags":["converter","annotation-introspector","custom-introspector","serialization","deserialization"],"backgroundTag":null,"analyzedSha":"87876ca5c0569b4933aec2d30d6225e4b9ba3a43","analyzedAt":"2026-08-11T12:55:24.033Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}