{"record":{"id":"aee1c523e878fb46","repo":"FasterXML/jackson-databind","slug":"invalid-abstract-type-resolution-from-to-la","errorCode":null,"errorMessage":"Invalid abstract type resolution from {} to {}: latter is not a subtype of former","messagePattern":"Invalid abstract type resolution from (.+?) to (.+?): latter is not a subtype of former","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/main/java/tools/jackson/databind/DeserializationConfig.java","lineNumber":600,"sourceCode":"     * @since 3.0\n     */\n    public JavaType mapAbstractType(JavaType type)\n    {\n        if (!hasAbstractTypeResolvers()) {\n            return type;\n        }\n        // first, general mappings\n        while (true) {\n            JavaType next = _mapAbstractType2(type);\n            if (next == null) {\n                return type;\n            }\n            // Should not have to worry about cycles; but better verify since they will invariably occur... :-)\n            // (also: guard against invalid resolution to a non-related type)\n            Class<?> prevCls = type.getRawClass();\n            Class<?> nextCls = next.getRawClass();\n            if ((prevCls == nextCls) || !prevCls.isAssignableFrom(nextCls)) {\n                throw new IllegalArgumentException(\"Invalid abstract type resolution from \"+type+\" to \"+next+\": latter is not a subtype of former\");\n            }\n            type = next;\n        }\n    }\n\n    /**\n     * Method that will find abstract type mapping for specified type, doing a single\n     * lookup through registered abstract type resolvers; will not do recursive lookups.\n     */\n    private JavaType _mapAbstractType2(JavaType type)\n    {\n        Class<?> currClass = type.getRawClass();\n        for (AbstractTypeResolver resolver : abstractTypeResolvers()) {\n            JavaType concrete = resolver.findTypeMapping(this, type);\n            if ((concrete != null) && !concrete.hasRawClass(currClass)) {\n                return concrete;\n            }\n        }","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/FasterXML/jackson-databind/blob/87876ca5c0569b4933aec2d30d6225e4b9ba3a43/src/main/java/tools/jackson/databind/DeserializationConfig.java#L582-L618","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\n@Override\npublic JavaType findTypeMapping(DeserializationConfig config, JavaType type) {\n    if (type.getRawClass() == Animal.class) {\n        return config.constructType(Vehicle.class); // Vehicle is NOT an Animal\n    }\n    return null;\n}\n// after\n@Override\npublic JavaType findTypeMapping(DeserializationConfig config, JavaType type) {\n    if (type.getRawClass() == Animal.class) {\n        return config.constructType(Dog.class); // Dog IS an Animal\n    }\n    return null;\n}","handlingStrategy":"validation","validationCode":"// Validate resolver mappings before registration\nJavaType result = resolver.findTypeMapping(config, type);\nif (result != null) {\n    Class<?> base = type.getRawClass();\n    Class<?> concrete = result.getRawClass();\n    if (!base.isAssignableFrom(concrete)) {\n        throw new IllegalStateException(concrete + \" is not a subtype of \" + base);\n    }\n}","typeGuard":"boolean isValidMapping(JavaType from, JavaType to) {\n    return to != null && from.getRawClass().isAssignableFrom(to.getRawClass());\n}","tryCatchPattern":null,"preventionTips":["Always assert isAssignableFrom in AbstractTypeResolver unit tests.","Document each mapping with its base type in your module.","Avoid mapping interfaces to unrelated concrete classes."],"tags":["abstract-type-mapping","deserialization","custom-module","polymorphic","type-system"],"backgroundTag":null,"analyzedSha":"87876ca5c0569b4933aec2d30d6225e4b9ba3a43","analyzedAt":"2026-08-11T12:55:24.033Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}