FasterXML/jackson-databind · error · IllegalArgumentException

Invalid abstract type resolution from

Error message

Invalid abstract type resolution from {} to {}: latter is not a subtype of former

What it means

Thrown by DeserializationConfig.mapAbstractType() when a registered AbstractTypeResolver maps an abstract type to a concrete type that is not a valid subtype. Jackson validates that the resolved concrete type must be assignable from the original abstract type to prevent nonsensical mappings and infinite resolution cycles.

Solutions

  1. Fix your AbstractTypeResolver.findTypeMapping() to return a JavaType whose raw class is a subtype of the input abstract type.
  2. Add a unit test that asserts resolver.findTypeMapping(type).getRawClass() is assignable from the original type for every mapping you register.
  3. Check for accidental type erasure where generics cause the resolver to lose track of the base type.

Example fix

// before
@Override
public JavaType findTypeMapping(DeserializationConfig config, JavaType type) {
    if (type.getRawClass() == Animal.class) {
        return config.constructType(Vehicle.class); // Vehicle is NOT an Animal
    }
    return null;
}
// after
@Override
public JavaType findTypeMapping(DeserializationConfig config, JavaType type) {
    if (type.getRawClass() == Animal.class) {
        return config.constructType(Dog.class); // Dog IS an Animal
    }
    return null;
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate resolver mappings before registration
JavaType result = resolver.findTypeMapping(config, type);
if (result != null) {
    Class<?> base = type.getRawClass();
    Class<?> concrete = result.getRawClass();
    if (!base.isAssignableFrom(concrete)) {
        throw new IllegalStateException(concrete + " is not a subtype of " + base);
    }
}

Type guard

boolean isValidMapping(JavaType from, JavaType to) {
    return to != null && from.getRawClass().isAssignableFrom(to.getRawClass());
}

Prevention

When it happens

Trigger: Registering a custom AbstractTypeResolver (via Module via addAbstractTypeResolver()) whose findTypeMapping() returns a JavaType whose raw class is not a subclass of the input type. For example, mapping List.class to a custom Map implementation.

Common situations: Custom Jackson modules that register abstract type mappings incorrectly. Bugs in AbstractTypeResolver implementations that return the wrong concrete type. Confusion about the direction of the isAssignableFrom check (concrete must extend abstract, not vice-versa).

Related errors


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

Appendix: source

Thrown at src/main/java/tools/jackson/databind/DeserializationConfig.java:600

     * @since 3.0
     */
    public JavaType mapAbstractType(JavaType type)
    {
        if (!hasAbstractTypeResolvers()) {
            return type;
        }
        // first, general mappings
        while (true) {
            JavaType next = _mapAbstractType2(type);
            if (next == null) {
                return type;
            }
            // Should not have to worry about cycles; but better verify since they will invariably occur... :-)
            // (also: guard against invalid resolution to a non-related type)
            Class<?> prevCls = type.getRawClass();
            Class<?> nextCls = next.getRawClass();
            if ((prevCls == nextCls) || !prevCls.isAssignableFrom(nextCls)) {
                throw new IllegalArgumentException("Invalid abstract type resolution from "+type+" to "+next+": latter is not a subtype of former");
            }
            type = next;
        }
    }

    /**
     * Method that will find abstract type mapping for specified type, doing a single
     * lookup through registered abstract type resolvers; will not do recursive lookups.
     */
    private JavaType _mapAbstractType2(JavaType type)
    {
        Class<?> currClass = type.getRawClass();
        for (AbstractTypeResolver resolver : abstractTypeResolvers()) {
            JavaType concrete = resolver.findTypeMapping(this, type);
            if ((concrete != null) && !concrete.hasRawClass(currClass)) {
                return concrete;
            }
        }

View on GitHub (pinned to 87876ca5c0)