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
- Fix your AbstractTypeResolver.findTypeMapping() to return a JavaType whose raw class is a subtype of the input abstract type.
- Add a unit test that asserts resolver.findTypeMapping(type).getRawClass() is assignable from the original type for every mapping you register.
- 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
- Always assert isAssignableFrom in AbstractTypeResolver unit tests.
- Document each mapping with its base type in your module.
- Avoid mapping interfaces to unrelated concrete classes.
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
- Only support `JavaType` implementation of `ResolvedType`…
- AnnotationIntrospector returned `Class<
- AnnotationIntrospector returned Class
- AnnotationIntrospector returned Class
- AnnotationIntrospector returned Converter definition of type
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)