FasterXML/jackson-databind · error · IllegalArgumentException
Trying to resolve a forward reference with id [${id}] that w
Error message
Trying to resolve a forward reference with id [${id}] that wasn't previously seen as unresolved. What it means
Thrown by CollectionDeserializer.CollectionReferringAccumulator.resolveForwardReference when an object identity (from @JsonIdentityInfo) is being resolved against a Collection, but the supplied id was never registered as an unresolved forward reference. Jackson only records ids that it could not immediately satisfy during parsing; resolving any other id is a programmer/input error. It surfaces as an IllegalArgumentException because the accumulator's pending list does not contain the id.
Source
Thrown at src/main/java/tools/jackson/databind/deser/jdk/CollectionDeserializer.java:659
public void resolveForwardReference(DeserializationContext ctxt, Object id, Object value) throws JacksonException
{
Iterator<CollectionReferring> iterator = _accumulator.iterator();
// Resolve ordering after resolution of an id. This mean either:
// 1- adding to the result collection in case of the first unresolved id.
// 2- merge the content of the resolved id with its previous unresolved id.
Collection<Object> previous = _result;
while (iterator.hasNext()) {
CollectionReferring ref = iterator.next();
if (ref.hasId(id)) {
iterator.remove();
previous.add(value);
previous.addAll(ref.next);
return;
}
previous = ref.next;
}
throw new IllegalArgumentException("Trying to resolve a forward reference with id [" + id
+ "] that wasn't previously seen as unresolved.");
}
/**
* Replace a resolved item in the result collection. Called when the bound
* item is rebound (e.g., builder → built object) via
* {@link Referring#handleItemRebind}.
*
* @param oldItem Item to replace (Builder)
* @param newItem Item to replace {@code oldItem} with (Built value)
*
* @since 3.2
*/
public void replaceResolvedItem(Object oldItem, Object newItem) {
if (_result instanceof List<?>) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) _result;
for (int i = 0, len = list.size(); i < len; i++) {View on GitHub (pinned to 87876ca5c0)
Solutions
- Inspect the JSON for the @ref whose id does not match any earlier @id in the same Collection, and correct the producer.
- If you call resolveForwardReferences manually, ensure you only resolve ids returned by UnresolvedForwardReference iterators from the same deserialization pass.
- Disable @JsonIdentityInfo on the element type (or scope it to a property) if forward references are not required, switching to default inline serialization.
- Add a custom DeserializationProblemHandler to log the offending id and continue instead of aborting.
Example fix
// before
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="@id")
public class Node { public List<Node> children; }
// JSON has {"@ref":42} but no prior {"@id":42}
// after: validate ids before resolving, or drop identity info
List<Node> nodes = mapper.readValue(json, new TypeReference<List<Node>>(){});
// ensure every @ref in the payload has a matching earlier @id Defensive patterns
Strategy: validation
Validate before calling
// Before resolving, confirm the id is actually pending.
UnresolvedForwardReference ufr = ...;
Object idToResolve = ...;
boolean known = false;
for (ReadableObjectId roid : ufr) { if (roid.hasId(idToResolve)) { known = true; break; } }
if (!known) { log.warn("Unknown forward-ref id {} ignored", idToResolve); }
else { roid.tryResolve(...); } Try / catch
try {
mapper.readerFor(Node.class).withAttribute(...).readValue(json);
} catch (UnresolvedForwardReference e) {
// do NOT blindly iterate; only resolve ids reported by e
Iterator<ReadableObjectId> it = e.iterator();
while (it.hasNext()) { ReadableObjectId r = it.next(); /* handle */ }
} Prevention
- Validate @JsonIdentityInfo payloads end-to-end on the producer side so every @ref has a prior @id.
- Never call resolveForwardReferences with ids obtained from sources other than the current UnresolvedForwardReference.
- Run a schema/round-trip test that re-serializes parsed output and diffs ids to catch duplicate refs early.
When it happens
Trigger: Deserializing JSON with @JsonIdentityInfo on a Collection element type where a "@ref" points to an id that was already resolved, never declared, or is a duplicate; or manually calling objectMapper.takeValueAsSomething followed by resolveForwardReferences with a stale/incorrect id on a Collection-typed property.
Common situations: Cyclic object graphs serialized with @JsonIdentityInfo where the JSON payload has been hand-edited or generated by a producer that emits duplicate @ref entries; round-tripping data through a cache that strips the first occurrence of an object; upgrading from a version that silently ignored unknown refs to one that enforces them.
Related errors
- Trying to resolve a forward reference with id [" + id + "] t
- Cannot deserialize Singleton container from "+size+" entries
- Could not resolve Object Id [{}] -- unresolved forward-refer
- Trying to resolve a forward reference with id [${id.toString
- Could not resolve Object Id [${id}] (for ${_beanType}).
AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11).
Data as JSON: /api/errors/a18310204a69ef2f.
Report an issue: GitHub.