google/gson · error · IllegalStateException

Adapter for type with cyclic dependency has been used…

Error message

Adapter for type with cyclic dependency has been used before dependency has been resolved

What it means

Thrown by Gson.FutureTypeAdapter.delegate() when a type adapter is used (read/write called) before its delegate has been set. FutureTypeAdapter is a placeholder created during resolution of cyclic type graphs (type A -> type B -> type A); the real adapter is wired later via setDelegate. Using it too early means the dependency cycle has not been resolved, so an IllegalStateException is thrown. It can also occur when a placeholder leaks to another thread before publication.

Solutions

  1. Do not invoke read()/write() on adapters obtained during create(); store them as fields and use them lazily at (de)serialization time, not at factory-construction time.
  2. Defer adapter lookup to the actual read/write call rather than resolving it inside create().
  3. Avoid sharing a Gson instance being constructed across threads; complete create() before publishing.
  4. If a cycle must be broken, introduce an InstanceCreator or a hand-written adapter that does not require the partner adapter during construction.

Example fix

// before: using adapter inside create() during cycle resolution
class MyFactory implements TypeAdapterFactory {
  public <T> TypeAdapter<T> create(Gson g, TypeToken<T> t) {
    TypeAdapter<Partner> p = g.getAdapter(Partner.class);
    p.fromJsonTree(...); // delegate not set -> throws
  }
}

// after: capture adapter, use lazily in read()/write()
TypeAdapter<Partner> p = g.getAdapter(Partner.class);
return new TypeAdapter<T>() {
  public T read(JsonReader in) { /* use p here, after resolution */ }
};
Defensive patterns

Strategy: validation

Validate before calling

// Do not use adapters obtained during create(); capture and use lazily.
class MyFactory implements TypeAdapterFactory {
  public <T> TypeAdapter<T> create(Gson g, TypeToken<T> t) {
    final TypeAdapter<Partner> partner = g.getAdapter(Partner.class); // OK to obtain
    return new TypeAdapter<T>() {
      public T read(JsonReader in) {
        // use partner here, AFTER construction
        Partner p = partner.read(in);
        return /* build T */;
      }
      public void write(JsonWriter out, T v) { /* use partner here */ }
    };
  }
}

Try / catch

try { adapter.read(reader); }
catch (IllegalStateException e) {
  if (e.getMessage().contains("cyclic dependency")) {
    // restructure factory to defer adapter usage out of create()
  } else throw e;
}

Prevention

When it happens

Trigger: Within a TypeAdapterFactory.create(), obtaining an adapter for a cyclically-dependent type and immediately using it to (de)serialize; calling gson.getAdapter() inside create() for the partner of a cycle and then invoking read/write on it; multithreaded construction where a FutureTypeAdapter is observed before setDelegate completes.

Common situations: Entity graphs with bidirectional references (Parent<->Child) where a factory eagerly serializes; custom factories that fetch and use adapters for self-referential types during create(); race conditions sharing a partially-built Gson across threads.

Related errors


AI-assisted analysis of google/gson@310ac341f2 (2026-08-10). Data as JSON: /api/errors/3994e1daa144bc64. Report an issue: GitHub.

Appendix: source

Thrown at gson/src/main/java/com/google/gson/Gson.java:1255

   * @see Gson#threadLocalAdapterResults
   */
  static class FutureTypeAdapter<T> extends SerializationDelegatingTypeAdapter<T> {
    private TypeAdapter<T> delegate = null;

    public void setDelegate(TypeAdapter<T> typeAdapter) {
      if (delegate != null) {
        throw new AssertionError("Delegate is already set");
      }
      delegate = typeAdapter;
    }

    private TypeAdapter<T> delegate() {
      TypeAdapter<T> delegate = this.delegate;
      if (delegate == null) {
        // Can occur when adapter is leaked to other thread or when adapter is used for
        // (de-)serialization
        // directly within the TypeAdapterFactory which requested it
        throw new IllegalStateException(
            "Adapter for type with cyclic dependency has been used"
                + " before dependency has been resolved");
      }
      return delegate;
    }

    @Override
    public TypeAdapter<T> getSerializationDelegate() {
      return delegate();
    }

    @Override
    public T read(JsonReader in) throws IOException {
      return delegate().read(in);
    }

    @Override
    public void write(JsonWriter out, T value) throws IOException {

View on GitHub (pinned to 310ac341f2)