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

ObjectIdReferenceProperty.PropertyReferring throws when handleResolvedForwardReference is invoked with an id that was not recorded as an unresolved forward reference. The property tracks pending ids; resolving an id the resolver never saw indicates the caller or stream is out of sync with the recorded pending set.

Source

Thrown at src/main/java/tools/jackson/databind/deser/impl/ObjectIdReferenceProperty.java:135

    public final static class PropertyReferring extends Referring {
        private final ObjectIdReferenceProperty _parent;
        public final Object _pojo;

        public PropertyReferring(ObjectIdReferenceProperty parent,
                UnresolvedForwardReference ref, Class<?> type, Object ob)
        {
            super(ref, type);
            _parent = parent;
            _pojo = ob;
        }

        @Override
        public void handleResolvedForwardReference(DeserializationContext ctxt,
                Object id, Object value) throws JacksonException
        {
            if (!hasId(id)) {
                throw new IllegalArgumentException("Trying to resolve a forward reference with id [" + id
                        + "] that wasn't previously seen as unresolved.");
            }
            // [databind#1496]: forward ref resolved, remove from pending set
            ctxt.removePendingForwardRef(_pojo);
            _parent.set(ctxt, _pojo, value);
        }
    }
}

View on GitHub (pinned to 87876ca5c0)

Solutions

  1. Let Jackson resolve object ids through the normal parse path; do not call handleResolvedForwardReference manually.
  2. If resolving manually, guard with hasId(id) before calling and skip unknown ids.
  3. Ensure the same ObjectMapper (same identity scheme) produces and consumes the JSON.
  4. Avoid sharing ObjectIdResolver state across concurrent parse operations.

Example fix

// before
referring.handleResolvedForwardReference(ctxt, id, value); // id not pending -> throws

// after
if (referring.hasId(id)) {
    referring.handleResolvedForwardReference(ctxt, id, value);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!referring.hasId(id)) {
    // id never pending; skip
    return;
}

Type guard

// no type guard; runtime membership via hasId

Try / catch

try { referring.handleResolvedForwardReference(ctxt, id, value); }
catch (IllegalArgumentException e) {
    if (e.getMessage().contains("wasn't previously seen as unresolved")) {
        // log and skip
    } else throw e;
}

Prevention

When it happens

Trigger: Manually driving ObjectIdResolver resolution with ids not produced during parsing; concurrent deserializers sharing state; a stream where an @id is referenced as resolved but was never seen as pending; partial replay of recorded forward references.

Common situations: Custom ObjectIdResolver implementations that call handleResolvedForwardReference directly; @JsonIdentityInfo combined with custom streaming code that bypasses normal id bookkeeping; tests that synthesize UnresolvedForwardReference instances.

Related errors


AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11). Data as JSON: /api/errors/5581c664d2fac0f5. Report an issue: GitHub.