FasterXML/jackson-databind · error · IllegalStateException

AnnotationIntrospector returned Class {}; expected Class<Key

Error message

AnnotationIntrospector returned Class {}; expected Class<KeyDeserializer>

What it means

Thrown by DeserializationContextExt.keyDeserializerInstance when the introspector returns a Class for the key deserializer, but that Class does not extend KeyDeserializer. Only KeyDeserializer subclasses can be instantiated to deserialize map keys.

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 a50c7d2a1d)

Solutions

  1. Make the referenced class extend KeyDeserializer and implement deserializeKey().
  2. If you meant a value deserializer, use @JsonDeserialize(using=...) instead.
  3. Verify the class hierarchy before wiring.

Example fix

// before
@JsonKeyDeserializer(ColorParser.class) // not a KeyDeserializer
class ColorParser { Color parse(String s){ ... } }
// after
class ColorKeyDeserializer extends KeyDeserializer {
    @Override public Object deserializeKey(String key, DeserializationContext ctxt){ ... }
}
@JsonKeyDeserializer(ColorKeyDeserializer.class)
Defensive patterns

Strategy: type-guard

Type guard

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

Prevention

When it happens

Trigger: @JsonKeyDeserializer(MyClass.class) where MyClass does not extend KeyDeserializer, or a custom introspector returning a non-KeyDeserializer Class.

Common situations: Pointing @JsonKeyDeserializer at a regular value deserializer or utility class; a refactor that dropped the extends clause; confusing value and key deserializer types.

Related errors


AI-assisted analysis of FasterXML/jackson-databind@a50c7d2a1d (2026-08-06). Data as JSON: /api/errors/7bd318a79ba120a8. Report an issue: GitHub.