{"record":{"id":"816c4dc5794b6b28","repo":"FasterXML/jackson-databind","slug":"annotationintrospector-returned-class-deserclas","errorCode":null,"errorMessage":"AnnotationIntrospector returned `Class<${deserClass.getName()}>`; expected `Class<ValueDeserializer>`","messagePattern":"AnnotationIntrospector returned `Class<(.+?)>`; expected `Class<ValueDeserializer>`","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java","lineNumber":256,"sourceCode":"        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        }\n        // First: need to resolve\n        deser.resolve(this);\n        return (ValueDeserializer<Object>) deser;\n    }\n\n    @Override\n    public final KeyDeserializer keyDeserializerInstance(Annotated ann, Object deserDef)\n    {\n        if (deserDef == null) {\n            return null;","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/FasterXML/jackson-databind/blob/87876ca5c0569b4933aec2d30d6225e4b9ba3a43/src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java#L238-L274","documentation":"Thrown when a custom AnnotationIntrospector (or @JsonDeserialize.using/as) returns a Class object as the deserializer definition, but that Class does not implement ValueDeserializer. Jackson validates the type before instantiating it via HandlerInstantiator or reflection, so this guards against a wrong class being wired in as a deserializer handler.","triggerScenarios":"Registering @JsonDeserialize(as = SomeNonDeserializerClass.class) on a property, or an AnnotationIntrospector.findDeserializer() override returning Class<?>.class where the class is not a ValueDeserializer (e.g. returning a serializer class, a factory, or a plain POJO by mistake).","commonSituations":"Copy-paste errors wiring @JsonDeserialize(as = ...) instead of @JsonSerialize(as = ...); returning the wrong Class reference from a custom introspector; returning a Class<MySerializer> where a deserializer was expected; classpath confusion after a refactor that renamed/moved classes.","solutions":["Verify the class referenced by @JsonDeserialize(as = ...) or returned by your AnnotationIntrospector actually implements tools.jackson.databind.ValueDeserializer.","If you meant to customize serialization, move the annotation to @JsonSerialize(using = ...)/@JsonSerialize(as = ...).","Return an instance (ValueDeserializer) instead of a Class, or return ValueDeserializer.None.class to signal 'no deserializer'.","If using a HandlerInstantiator, ensure it returns a ValueDeserializer instance and that the registered class implements ValueDeserializer."],"exampleFix":"// before\n@JsonDeserialize(as = MySerializer.class) // wrong: serializer, not deserializer\nprivate Foo foo;\n\n// after\n@JsonDeserialize(using = MyFooDeserializer.class) // must extend ValueDeserializer<Foo>\nprivate Foo foo;","handlingStrategy":"validation","validationCode":"Class<?> c = (Class<?>) def;\nif (c != null && c != ValueDeserializer.None.class\n        && !ValueDeserializer.class.isAssignableFrom(c)) {\n    throw new IllegalStateException(\"Not a ValueDeserializer: \" + c);\n}","typeGuard":"static boolean isValidDeserializerDef(Object def) {\n    return def == null\n        || def instanceof ValueDeserializer\n        || (def instanceof Class<?> c\n            && (c == ValueDeserializer.None.class\n                || ValueDeserializer.class.isAssignableFrom(c)));\n}","tryCatchPattern":"try { mapper.readValue(json, Foo.class); }\ncatch (IllegalStateException e) {\n    if (e.getMessage().contains(\"expected `Class<ValueDeserializer>`\")) {\n        // fix the @JsonDeserialize(as=...) wiring\n    } else throw e;\n}","preventionTips":["Always annotate deserializers with @JsonDeserialize(using=...) and serializers with @JsonSerialize(...); never swap as/using blindly.","Unit-test custom AnnotationIntrospector overrides to assert the returned type for each find* method."],"tags":["deserialization","annotation-introspector","configuration","custom-deserializer"],"backgroundTag":null,"analyzedSha":"87876ca5c0569b4933aec2d30d6225e4b9ba3a43","analyzedAt":"2026-08-11T12:55:24.033Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}