{"record":{"id":"8e9e5a1d3e851cf4","repo":"FasterXML/jackson-databind","slug":"trying-to-resolve-a-forward-reference-with-id","errorCode":null,"errorMessage":"Trying to resolve a forward reference with id [\" + id + \"] that wasn't previously seen as unresolved.","messagePattern":"Trying to resolve a forward reference with id \\[\" \\+ id \\+ \"\\] that wasn't previously seen as unresolved\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/main/java/tools/jackson/databind/deser/jdk/MapDeserializer.java","lineNumber":1036,"sourceCode":"            throws JacksonException\n        {\n            Iterator<MapReferring> iterator = _accumulator.iterator();\n            // Resolve ordering after resolution of an id. This means either:\n            // 1- adding to the result map in case of the first unresolved id.\n            // 2- merge the content of the resolved id with its previous unresolved id.\n            Map<Object,Object> previous = _result;\n            while (iterator.hasNext()) {\n                MapReferring ref = iterator.next();\n                if (ref.hasId(id)) {\n                    iterator.remove();\n                    previous.put(ref.key, value);\n                    previous.putAll(ref.next);\n                    return;\n                }\n                previous = ref.next;\n            }\n\n            throw new IllegalArgumentException(\"Trying to resolve a forward reference with id [\" + id\n                    + \"] that wasn't previously seen as unresolved.\");\n        }\n\n        /**\n         * Replace a resolved item in the result map. Called when the bound item\n         * is rebound (e.g., builder → built object).\n         *\n         * @param oldItem Item to replace (Builder)\n         * @param newItem Item to replace {@code oldItem} with (Built value)\n         *\n         * @since 3.2\n         */\n        public void replaceResolvedItem(Object oldItem, Object newItem) {\n            replaceInMap(_result, oldItem, newItem);\n            // Pending accumulator entries may also hold the old item if a later\n            // forward ref hasn't yet resolved.\n            for (MapReferring ref : _accumulator) {\n                replaceInMap(ref.next, oldItem, newItem);","sourceCodeStart":1018,"sourceCodeEnd":1054,"githubUrl":"https://github.com/FasterXML/jackson-databind/blob/87876ca5c0569b4933aec2d30d6225e4b9ba3a43/src/main/java/tools/jackson/databind/deser/jdk/MapDeserializer.java#L1018-L1054","documentation":"MapDeserializer.MapReferringAccumulator.resolveForwardReference is the Map analog of the Collection forward-reference accumulator. When @JsonIdentityInfo is used on Map values, unresolved ids are recorded; resolving an id that was never registered (already resolved, unknown, or duplicate) throws IllegalArgumentException. The id is included in the message.","triggerScenarios":"Deserializing JSON with @JsonIdentityInfo on a Map value type where a \"@ref\" key references an id never declared as unresolved; manually invoking resolveForwardReferences with an id not present in the pending accumulator; cyclic Map values whose JSON was tampered with.","commonSituations":"Bi-directional relationships stored in Map<K, Entity> with @JsonIdentityInfo where the producer emits a @ref before the matching @id or emits duplicate @ref entries; importing JSON from another serializer that does not honor Jackson identity semantics; partial deserialization that drops the first occurrence.","solutions":["Validate that every @ref in the payload has exactly one earlier @id on a Map value, and remove duplicates.","Avoid calling resolveForwardReferences with ids you did not obtain from the current UnresolvedForwardReference handle.","Drop @JsonIdentityInfo on the Map value type if forward references are not required.","Use a custom DeserializationProblemHandler to skip or log unknown refs during tolerant parsing."],"exampleFix":"// before\n@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property=\"@id\")\npublic class Cache { public Map<String, Item> items; }\n// JSON references @ref:\"x9\" but no @id:\"x9\" exists\n\n// after: ensure every @ref has a matching earlier @id, or remove @JsonIdentityInfo","handlingStrategy":"validation","validationCode":"// Before resolving, confirm the id is in the Map accumulator's pending set.\nObject idToResolve = ...;\nboolean known = pendingMapReferringIds.contains(idToResolve);\nif (!known) { log.warn(\"Unknown Map forward-ref id {} ignored\", idToResolve); }\nelse { accumulator.resolveForwardReference(ctxt, idToResolve, value); }","typeGuard":null,"tryCatchPattern":"try {\n    mapper.readValue(json, new TypeReference<Map<String,Entity>>(){});\n} catch (UnresolvedForwardReference e) {\n    Iterator<ReadableObjectId> it = e.iterator();\n    while (it.hasNext()) { ReadableObjectId r = it.next(); /* resolve only reported ids */ }\n}","preventionTips":["Ensure JSON producers emit @id before any @ref for Map values.","Do not mix hand-written @ref ids with Jackson-managed identity state.","Round-trip test cyclic Map graphs and assert no UnresolvedForwardReference escapes."],"tags":["deserialization","json-identity","maps","forward-reference","illegal-argument"],"backgroundTag":null,"analyzedSha":"87876ca5c0569b4933aec2d30d6225e4b9ba3a43","analyzedAt":"2026-08-11T12:55:24.033Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}