{"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/a50c7d2a1d57234ac4adf70dbd88ac90db6436e4/src/main/java/tools/jackson/databind/DatabindContext.java#L443-L479","documentation":"During introspection, the AnnotationIntrospector returned a value for a @JsonSerialize/@JsonDeserialize 'converter' (or 'using') attribute that is neither a Converter instance nor a Class object. DatabindContext.converterInstance() can only resolve those two shapes, so any other type is a contract violation by the introspector. The message names the offending runtime class so you can identify which custom introspector logic produced the bad value.","triggerScenarios":"A custom AnnotationIntrospector override (e.g. findSerializationConverter / findDeserializationConverter) returns a non-Converter, non-Class object such as a String, a Type, a Method, or a wrapper/holder bean. This typically only happens with hand-written introspectors or mixins that programmatically synthesize converter definitions.","commonSituations":"Migrating from 2.x where introspector return contracts were looser; building a dynamic introspector that looks up converters by name string and forgets to Class.forName()/instantiate them; passing a lambda or Supplier instead of the actual Converter instance; a custom annotation whose value type changed between versions being read raw into the converter slot.","solutions":["Inspect the full exception stack to find which AnnotationIntrospector.findXxxConverter() method is on the stack and what it returns.","Ensure that method returns either a Converter<?,?> instance or a Class<? extends Converter> (or Converter.None.class to signal 'none').","If you intended to reference a converter by name, resolve the name to a Class (or instance) inside the introspector before returning it.","Add a unit test asserting the introspector's converter methods only ever return those two types."],"exampleFix":"// before\n@Override\npublic Object findDeserializationConverter(Annotated a) {\n    return a.getAnnotation(MyConvert.class).value(); // returns a String name\n}\n// after\n@Override\npublic Object findDeserializationConverter(Annotated a) {\n    String name = a.getAnnotation(MyConvert.class).value();\n    if (name.isEmpty()) return null;\n    try { return Class.forName(name); } // return a Class<? extends Converter>\n    catch (ClassNotFoundException e) { return null; }\n}","handlingStrategy":"type-guard","validationCode":"// Validate an introspector's converter return before it is consumed\nstatic Object safeConverterDef(Object def) {\n    if (def == null) return null;\n    if (def instanceof Converter<?,?>) return def;\n    if (def instanceof Class<?> c && Converter.class.isAssignableFrom(c)) return def;\n    throw new IllegalStateException(\"Bad converter def: \" + def.getClass());\n}","typeGuard":"boolean isConverterDef(Object o) {\n    return o == null\n        || o instanceof Converter<?,?>\n        || (o instanceof Class<?> c && Converter.class.isAssignableFrom(c));\n}","tryCatchPattern":null,"preventionTips":["Unit-test custom AnnotationIntrospector.findXxxConverter() methods to assert they only return Converter, Class<? extends Converter>, Converter.None.class, or null.","Type your introspector's internal holder fields as Class<? extends Converter<?,?>> so the compiler catches mismatches.","Audit mixins and introspectors during 2.x-to-3.x migration for raw Object converter returns."],"tags":["introspection","converter","annotation-introspector","configuration"],"analyzedSha":"a50c7d2a1d57234ac4adf70dbd88ac90db6436e4","analyzedAt":"2026-08-06T20:31:51.404Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}