{"id":"3994e1daa144bc64","repo":"google/gson","slug":"adapter-for-type-with-cyclic-dependency-has-been-u","errorCode":null,"errorMessage":"Adapter for type with cyclic dependency has been used before dependency has been resolved","messagePattern":"Adapter for type with cyclic dependency has been used before dependency has been resolved","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"critical","filePath":"gson/src/main/java/com/google/gson/Gson.java","lineNumber":1255,"sourceCode":"   * @see Gson#threadLocalAdapterResults\n   */\n  static class FutureTypeAdapter<T> extends SerializationDelegatingTypeAdapter<T> {\n    private TypeAdapter<T> delegate = null;\n\n    public void setDelegate(TypeAdapter<T> typeAdapter) {\n      if (delegate != null) {\n        throw new AssertionError(\"Delegate is already set\");\n      }\n      delegate = typeAdapter;\n    }\n\n    private TypeAdapter<T> delegate() {\n      TypeAdapter<T> delegate = this.delegate;\n      if (delegate == null) {\n        // Can occur when adapter is leaked to other thread or when adapter is used for\n        // (de-)serialization\n        // directly within the TypeAdapterFactory which requested it\n        throw new IllegalStateException(\n            \"Adapter for type with cyclic dependency has been used\"\n                + \" before dependency has been resolved\");\n      }\n      return delegate;\n    }\n\n    @Override\n    public TypeAdapter<T> getSerializationDelegate() {\n      return delegate();\n    }\n\n    @Override\n    public T read(JsonReader in) throws IOException {\n      return delegate().read(in);\n    }\n\n    @Override\n    public void write(JsonWriter out, T value) throws IOException {","sourceCodeStart":1237,"sourceCodeEnd":1273,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/gson/src/main/java/com/google/gson/Gson.java#L1237-L1273","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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."],"exampleFix":"// before: using an adapter inside create() for a still-resolving cyclic type\nclass NodeAdapterFactory implements TypeAdapterFactory {\n  public <T> TypeAdapter<T> create(Gson g, TypeToken<T> t) {\n    TypeAdapter<T> a = g.getAdapter(t); // returns FutureTypeAdapter for the cycle\n    a.write(...); // throws: delegate not resolved yet\n    return ...;\n  }\n}\n\n// after: capture lazily, use only after create() returns\nclass NodeAdapterFactory implements TypeAdapterFactory {\n  public <T> TypeAdapter<T> create(Gson g, TypeToken<T> t) {\n    return new TypeAdapter<T>() {\n      public void write(JsonWriter w, T v) { /* use g.getAdapter(...) here, at call time */ }\n      public T read(JsonReader r) { return null; }\n    };\n  }\n}","handlingStrategy":"validation","validationCode":"// Ensure Gson is fully built before publishing; do not use adapters inside create()\nGson gson = new GsonBuilder().registerTypeAdapterFactory(myFactory).create();\n// Only NOW share 'gson' with other threads; never inside myFactory.create().","typeGuard":null,"tryCatchPattern":"try {\n  String json = gson.toJson(obj);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"cyclic dependency\")) {\n    // adapter used before resolution; restructure to access adapters lazily, not in create()\n  } else throw e;\n}","preventionTips":["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."],"tags":["gson-core","type-adapter","cyclic-dependency","threading","initialization"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}