FasterXML/jackson-databind · error · UnresolvedForwardReference

Could not resolve Object Id [${id}] (for ${_beanType}).

Error message

Could not resolve Object Id [${id}] (for ${_beanType}).

What it means

Thrown as an UnresolvedForwardReference when a JSON Object Id referenced in the input could not be resolved by the end of deserialization. With @JsonIdentityInfo, an @id with no preceding object definition leaves a pending forward reference; Jackson raises this when it tries to materialize the referenced object but the resolver has no entry.

Source

Thrown at src/main/java/tools/jackson/databind/deser/bean/BeanDeserializerBase.java:1653

    protected Object deserializeWithObjectId(JsonParser p, DeserializationContext ctxt)
        throws JacksonException
    {
        return deserializeFromObject(p, ctxt);
    }

    /**
     * Method called in cases where it looks like we got an Object Id
     * to parse and use as a reference.
     */
    protected Object deserializeFromObjectId(JsonParser p, DeserializationContext ctxt)
        throws JacksonException
    {
        Object id = _objectIdReader.readObjectReference(p, ctxt);
        ReadableObjectId roid = ctxt.findObjectId(id, _objectIdReader.generator, _objectIdReader.resolver);
        // do we have it resolved?
        Object pojo = roid.resolve();
        if (pojo == null) { // not yet; should wait...
            throw new UnresolvedForwardReference(p,
                    "Could not resolve Object Id ["+id+"] (for "+_beanType+").",
                    p.currentLocation(), roid);
        }
        return pojo;
    }

    protected Object deserializeFromObjectUsingNonDefault(JsonParser p,
            DeserializationContext ctxt)
        throws JacksonException
    {
        // 02-Jul-2024, tatu: [databind#4602] Need to tweak regular and "array" delegating
        //   Creator handling
        final ValueDeserializer<Object> delegateDeser = _delegateDeserializer(p);
        if (delegateDeser != null) {
            // [databind#5909]: signal delegate-bind-pending so that any ROID
            // bound during delegate deserialization retains resolved Referrings
            // — needed for collection-property forward refs to be rebound after
            // updateObjectId(delegate, bean) below.

View on GitHub (pinned to 87876ca5c0)

Solutions

  1. Ensure the JSON was serialized with the same @JsonIdentityInfo (generator/resolver/property name) as the reader.
  2. If forward references are legitimate, catch UnresolvedForwardReference and resolve them after the fact via handleResolvedForwardReference.
  3. Provide a custom ObjectIdResolver (scope=SCOPE_SINGLETON or a custom implementation) that retains resolved objects across the stream.
  4. Check for truncated/malformed JSON where the referenced object definition is missing.

Example fix

// before
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
class Node { Node next; } // reading JSON whose @ref has no object -> throws

// after (ensure writer used same scheme, or resolve manually)
try {
    Node n = mapper.readValue(json, Node.class);
} catch (UnresolvedForwardReference e) {
    ReadableObjectId roid = e.getRoid();
    roid.bindItem(fallbackObject);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before reading, confirm the resolver can answer; generally not possible
// pre-validate by ensuring writer config matches reader @JsonIdentityInfo

Type guard

// no compile-time guard; identity references are runtime-resolved

Try / catch

try { mapper.readValue(json, Node.class); }
catch (UnresolvedForwardReference e) {
    ReadableObjectId roid = e.getRoid();
    // supply the missing object or report the dangling @id
}

Prevention

When it happens

Trigger: JSON contains {"@id":99,"@ref":99} where the referenced object was never defined; circular references serialized with an incompatible identity scheme; deserializing a fragment that references an object defined elsewhere in a stream that was not parsed; custom ObjectIdResolver that does not retain objects.

Common situations: Mixing serialized output from one mapper version/config with a reader configured differently; truncated JSON; custom ObjectIdResolver with a scope that discards resolved items; @JsonIdentityInfo on bidirectional entities where only one side was serialized.

Related errors


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