{"record":{"id":"cad2ba2df6a527c4","repo":"FasterXML/jackson-databind","slug":"annotationintrospector-returned-deserializer-defin","errorCode":null,"errorMessage":"AnnotationIntrospector returned deserializer definition of type ${deserDef.getClass().getName()}; expected type `ValueDeserializer` or `Class<ValueDeserializer>` instead","messagePattern":"AnnotationIntrospector returned deserializer definition of type (.+?); expected type `ValueDeserializer` or `Class<ValueDeserializer>` instead","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java","lineNumber":246,"sourceCode":"    /**********************************************************************\n     */\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public ValueDeserializer<Object> deserializerInstance(Annotated ann, Object deserDef)\n    {\n        if (deserDef == null) {\n            return null;\n        }\n        ValueDeserializer<?> deser;\n\n        if (deserDef instanceof ValueDeserializer valueDeserializer) {\n            deser = valueDeserializer;\n        } else {\n            // Alas, there's no way to force return type of \"either class\n            // X or Y\" -- need to throw an exception after the fact\n            if (!(deserDef instanceof Class)) {\n                throw new IllegalStateException(\"AnnotationIntrospector returned deserializer definition of type \"\n                        +deserDef.getClass().getName()\n                        +\"; expected type `ValueDeserializer` or `Class<ValueDeserializer>` instead\");\n            }\n            Class<?> deserClass = (Class<?>)deserDef;\n            // there are some known \"no class\" markers to consider too:\n            if (deserClass == ValueDeserializer.None.class || ClassUtil.isBogusClass(deserClass)) {\n                return null;\n            }\n            if (!ValueDeserializer.class.isAssignableFrom(deserClass)) {\n                throw new IllegalStateException(\"AnnotationIntrospector returned `Class<\"+deserClass.getName()+\">`; expected `Class<ValueDeserializer>`\");\n            }\n            HandlerInstantiator hi = _config.getHandlerInstantiator();\n            deser = (hi == null) ? null : hi.deserializerInstance(_config, ann, deserClass);\n            if (deser == null) {\n                deser = (ValueDeserializer<?>) ClassUtil.createInstance(deserClass,\n                        _config.canOverrideAccessModifiers());\n            }\n        }","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/FasterXML/jackson-databind/blob/87876ca5c0569b4933aec2d30d6225e4b9ba3a43/src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java#L228-L264","documentation":"Thrown by DeserializationContextExt.deserializerInstance when an AnnotationIntrospector returned a deserializer definition whose runtime type is neither a ValueDeserializer instance nor a Class. Jackson expects @JsonDeserialize(using=...) (or the introspector equivalent) to resolve to a ValueDeserializer or a Class<ValueDeserializer>; anything else is treated as an introspector/annotation bug.","triggerScenarios":"A custom AnnotationIntrospector's findDeserializer returns an arbitrary object (a String, Type, bean, etc.) instead of a ValueDeserializer or Class; a framework/annotation remapper leaking the wrong object into the deserializer slot.","commonSituations":"Writing a custom introspector and returning the wrong field; an annotation-driven framework that wraps @JsonDeserialize and returns a descriptor object instead of a class; upgrading Jackson where the introspector contract was tightened to disallow non-Class/non-ValueDeserializer returns.","solutions":["Fix the custom AnnotationIntrospector so findDeserializer returns a ValueDeserializer instance or a Class<? extends ValueDeserializer> (or ValueDeserializer.None, or null).","Ensure @JsonDeserialize(using=X.class) points to a ValueDeserializer subclass and is not being rewritten by a mixin/introspector.","Remove the introspector override that is rewriting deserializer definitions if it is incorrect."],"exampleFix":"// before: introspector returns a String\n@Override\npublic Object findDeserializer(Annotated a) {\n    return a.getName();\n}\n\n// after: returns a Class<? extends ValueDeserializer>\n@Override\npublic Object findDeserializer(Annotated a) {\n    return MyValueDeserializer.class;\n}","handlingStrategy":"type-guard","validationCode":"Object def = introspector.findDeserializer(annotated);\nif (def != null && !(def instanceof ValueDeserializer) && !(def instanceof Class)) {\n    throw new IllegalStateException(\"Bad deserializer definition: \" + def.getClass());\n}","typeGuard":"boolean isDeserializerDef(Object d) {\n    return d == null || d instanceof ValueDeserializer<?> || d instanceof Class;\n}","tryCatchPattern":null,"preventionTips":["Custom introspectors must return ValueDeserializer, Class<? extends ValueDeserializer>, ValueDeserializer.None, or null.","Ensure @JsonDeserialize(using=X.class) always names a ValueDeserializer subclass.","Add unit tests for introspector return values on every annotated element."],"tags":["jackson","introspector","deserializer","annotation","config"],"backgroundTag":null,"analyzedSha":"87876ca5c0569b4933aec2d30d6225e4b9ba3a43","analyzedAt":"2026-08-11T12:55:24.033Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}