google/gson · critical · IllegalStateException

Adapter for type with cyclic dependency has been used before

Error message

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

What it means

Thrown by FutureTypeAdapter.delegate() when a type adapter for a cyclically-dependent type is used (its read/write called) before the real delegate adapter has been set. Gson uses FutureTypeAdapter as a placeholder while resolving type graphs like A->B->A; normally the delegate is resolved before any use. Using it early means the cycle was not fully resolved, typically because the adapter was leaked to another thread or invoked directly inside the very factory that requested it. It is an IllegalStateException.

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 8b8628c656)

Solutions

  1. Do NOT call read()/write() on adapters obtained during TypeAdapterFactory.create(); only capture them for later use.
  2. Ensure the Gson instance is fully constructed (GsonBuilder.create() returned) before sharing it across threads.
  3. If you have a cyclic type graph, let Gson resolve it; access adapters lazily (store the Gson/TypeToken and call getAdapter at use time, not at create time).
  4. Restructure the cyclic dependency (e.g. via @JsonAdapter or a dedicated non-cyclic adapter) if resolution genuinely cannot complete.

Example fix

// before: using an adapter inside create() for a still-resolving cyclic type
class NodeAdapterFactory implements TypeAdapterFactory {
  public <T> TypeAdapter<T> create(Gson g, TypeToken<T> t) {
    TypeAdapter<T> a = g.getAdapter(t); // returns FutureTypeAdapter for the cycle
    a.write(...); // throws: delegate not resolved yet
    return ...;
  }
}

// after: capture lazily, use only after create() returns
class NodeAdapterFactory implements TypeAdapterFactory {
  public <T> TypeAdapter<T> create(Gson g, TypeToken<T> t) {
    return new TypeAdapter<T>() {
      public void write(JsonWriter w, T v) { /* use g.getAdapter(...) here, at call time */ }
      public T read(JsonReader r) { return null; }
    };
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure Gson is fully built before publishing; do not use adapters inside create()
Gson gson = new GsonBuilder().registerTypeAdapterFactory(myFactory).create();
// Only NOW share 'gson' with other threads; never inside myFactory.create().

Try / catch

try {
  String json = gson.toJson(obj);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("cyclic dependency")) {
    // adapter used before resolution; restructure to access adapters lazily, not in create()
  } else throw e;
}

Prevention

When it happens

Trigger: Inside a TypeAdapterFactory.create(), calling adapter.read()/write() on the adapter returned by gson.getAdapter() for a type that is still being resolved (re-entrant resolution of a cycle); publishing the Gson instance or its adapters to another thread before construction completes; an adapter that eagerly deserializes during factory.create().

Common situations: Custom factories that recursively resolve types with mutual references (e.g. Tree<Node> where Node has Tree children) and try to use the adapter inline; multi-threaded initialization where one thread uses the Gson before another finishes building it; adapters that bootstrapping themselves via getAdapter during create(); regression after upgrading Gson affecting cycle handling.

Related errors


AI-assisted analysis of google/gson@8b8628c656 (2026-08-04). Data as JSON: /data/errors/3994e1daa144bc64.json. Report an issue: GitHub.