FasterXML/jackson-databind · error · IllegalStateException

AnnotationIntrospector returned Class

Error message

AnnotationIntrospector returned Class ${deserClass.getName()}; expected Class<KeyDeserializer>

What it means

Thrown when keyDeserializerInstance receives a Class as the key-deserializer definition, but that Class does not extend KeyDeserializer. This is the class-typed sibling of error 41: the value is a Class (so it passed the first check) but it is the wrong kind of class.

Solutions

  1. Ensure the referenced class extends tools.jackson.databind.KeyDeserializer and overrides deserializeKey(String, DeserializationContext).
  2. If you intended a value deserializer, register it via @JsonDeserialize(contentUsing = ...) instead of keyUsing.
  3. Return KeyDeserializer.None.class or null to disable the custom key deserializer.

Example fix

// before
@JsonDeserialize(keyUsing = MyValueDeserializer.class)
Map<String,V> map; // MyValueDeserializer extends ValueDeserializer, not KeyDeserializer

// after
@JsonDeserialize(keyUsing = MyKeyDeserializer.class)
Map<String,V> map; // MyKeyDeserializer extends KeyDeserializer
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = (Class<?>) def;
if (c != null && c != KeyDeserializer.None.class
        && !KeyDeserializer.class.isAssignableFrom(c)) {
    throw new IllegalStateException("Not a KeyDeserializer: " + c);
}

Type guard

static boolean isKeyDeserializerClass(Class<?> c) {
    return c != null && c != KeyDeserializer.None.class
        && KeyDeserializer.class.isAssignableFrom(c);
}

Try / catch

try { mapper.readValue(json, mapType); }
catch (IllegalStateException e) {
    if (e.getMessage().contains("expected Class<KeyDeserializer>")) {
        // referenced class must extend KeyDeserializer
    } else throw e;
}

Prevention

When it happens

Trigger: @JsonDeserialize(keyUsing = SomeClass.class) where SomeClass does not extend KeyDeserializer; an AnnotationIntrospector returning Class<ValueDeserializer> or any unrelated Class for a map key deserializer.

Common situations: Reusing a value deserializer class for map keys; forgetting that key deserializers must extend KeyDeserializer specifically; refactor that changed a class's hierarchy after it was referenced in annotations.

Related errors


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

Appendix: source

Thrown at src/main/java/tools/jackson/databind/deser/DeserializationContextExt.java:293

        }

        KeyDeserializer deser;

        if (deserDef instanceof KeyDeserializer keyDeserializer) {
            deser = keyDeserializer;
        } else {
            if (!(deserDef instanceof Class)) {
                throw new IllegalStateException("AnnotationIntrospector returned key deserializer definition of type "
                        +deserDef.getClass().getName()
                        +"; expected type KeyDeserializer or Class<KeyDeserializer> instead");
            }
            Class<?> deserClass = (Class<?>)deserDef;
            // there are some known "no class" markers to consider too:
            if (deserClass == KeyDeserializer.None.class || ClassUtil.isBogusClass(deserClass)) {
                return null;
            }
            if (!KeyDeserializer.class.isAssignableFrom(deserClass)) {
                throw new IllegalStateException("AnnotationIntrospector returned Class "+deserClass.getName()
                        +"; expected Class<KeyDeserializer>");
            }
            HandlerInstantiator hi = _config.getHandlerInstantiator();
            deser = (hi == null) ? null : hi.keyDeserializerInstance(_config, ann, deserClass);
            if (deser == null) {
                deser = (KeyDeserializer) ClassUtil.createInstance(deserClass,
                        _config.canOverrideAccessModifiers());
            }
        }
        // First: need to resolve
        deser.resolve(this);
        return deser;
    }

    /*
    /**********************************************************************
    /* Extended API, read methods
    /**********************************************************************

View on GitHub (pinned to 87876ca5c0)