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
- Do NOT call read()/write() on adapters obtained during TypeAdapterFactory.create(); only capture them for later use.
- Ensure the Gson instance is fully constructed (GsonBuilder.create() returned) before sharing it across threads.
- 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).
- 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
- Never call read()/write() on adapters obtained during TypeAdapterFactory.create(); capture them for deferred use.
- Publish the Gson instance to other threads only after GsonBuilder.create() returns.
- For cyclic type graphs, resolve adapters lazily at first read/write, not at construction.
- Add an integration test serializing the cyclic type to catch resolution-order regressions.
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
- GSON ({GsonBuildConfig.VERSION}) cannot handle {type}
- GSON cannot serialize or deserialize {type}
- Type adapter '{typeAdapter}' returned wrong type; requested
- JSON document was not fully consumed.
- Class {typeAdapter.getClass().getName()} does not implement
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/3994e1daa144bc64.json.
Report an issue: GitHub.