{"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/a50c7d2a1d57234ac4adf70dbd88ac90db6436e4/src/main/java/tools/jackson/databind/DeserializationConfig.java#L582-L618","documentation":"DeserializationConfig.mapAbstractType() walks the registered AbstractTypeResolvers to remap an abstract type to a concrete one, and requires each step to produce a true subtype of the source type. When a resolver returns a type whose raw class is NOT assignable to the previous type, Jackson throws this IllegalArgumentException because the mapping would break type safety. The message names both the source type and the (invalid) target so you can see exactly which mapping is wrong.","triggerScenarios":"Registering an AbstractTypeResolver (or Module that adds one, e.g. via SimpleModule.addAbstractTypeResolver / setAbstractTypeResolver) whose findTypeMapping returns an unrelated concrete type; a custom mixin-style remapping that maps e.g. List -> ArrayList is fine but Map -> SomeBean is not; two resolvers chained so the second receives an already-remapped type it was not written for.","commonSituations":"Migrating type mappings from 2.x where assignability checks were less strict; copying an AbstractTypeResolver from another project where the type hierarchy differed; using a resolver to 'swap' types for legacy compatibility without ensuring the target extends/implements the source; an accidental resolver registration left over from removed classes.","solutions":["In your AbstractTypeResolver.findTypeMapping, guarantee the returned JavaType's raw class is assignable from (subtype of) the input raw class.","If you genuinely need an incompatible target, you need a custom ValueDeserializer or @JsonDeserialize(as=...) on a compatible type, not an abstract type remap.","Audit all registered AbstractTypeResolvers (SimpleModule.getAbstractTypeResolvers / mapper's module list) and check each mapping's assignability with type.isAssignableFrom(concrete).","Add a test that calls config.mapAbstractType(...) for each mapping and asserts the result is a subtype."],"exampleFix":"// before\n@Override\npublic JavaType findTypeMapping(DeserializationConfig config, JavaType type) {\n    if (type.getRawClass() == Animal.class) {\n        return config.getTypeFactory().constructType(Vehicle.class); // Vehicle 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.getTypeFactory().constructType(Dog.class); // Dog extends Animal\n    }\n    return null;\n}","handlingStrategy":"validation","validationCode":"// Verify an AbstractTypeResolver mapping is type-safe before registering\nstatic JavaType safeMap(DeserializationConfig cfg, AbstractTypeResolver r, JavaType from) {\n    JavaType to = r.findTypeMapping(cfg, from);\n    if (to != null && !from.getRawClass().isAssignableFrom(to.getRawClass())) {\n        throw new IllegalStateException(from + \" -> \" + to + \" is not a subtype\");\n    }\n    return to;\n}","typeGuard":"boolean isValidAbstractMap(JavaType from, JavaType to) {\n    return to != null && from.getRawClass().isAssignableFrom(to.getRawClass());\n}","tryCatchPattern":"try {\n    mapper.readValue(json, AbstractType.class);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"not a subtype of former\")) {\n        // log and surface the offending resolver mapping\n    }\n    throw e;\n}","preventionTips":["Unit-test each AbstractTypeResolver.findTypeMapping() asserting the returned type is assignable to the input.","Audit chained resolvers; each step must preserve assignability.","Prefer @JsonDeserialize(as=ConcreteSubtype.class) on compatible types over broad abstract remaps."],"tags":["deserialization","abstract-type","type-resolution","configuration"],"analyzedSha":"a50c7d2a1d57234ac4adf70dbd88ac90db6436e4","analyzedAt":"2026-08-06T20:31:51.404Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}