{"record":{"id":"21d1ea3ef361e4ec","repo":"google/gson","slug":"invalid-attempt-to-bind-an-instance-of-classname","errorCode":null,"errorMessage":"Invalid attempt to bind an instance of ${className} as a @JsonAdapter for ${type}. @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory, JsonSerializer or JsonDeserializer.","messagePattern":"Invalid attempt to bind an instance of (.+?) as a @JsonAdapter for (.+?)\\. @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory, JsonSerializer or JsonDeserializer\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java","lineNumber":148,"sourceCode":"\n      // Uses dummy factory instances because TreeTypeAdapter needs a 'skipPast' factory for\n      // `Gson.getDelegateAdapter` call and has to differentiate there whether TreeTypeAdapter was\n      // created for @JsonAdapter on class or field\n      TypeAdapterFactory skipPast;\n      if (isClassAnnotation) {\n        skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY;\n      } else {\n        skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY;\n      }\n      @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n      TypeAdapter<?> tempAdapter =\n          new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe);\n      typeAdapter = tempAdapter;\n\n      // TreeTypeAdapter handles nullSafe; don't additionally call `nullSafe()`\n      nullSafe = false;\n    } else {\n      throw new IllegalArgumentException(\n          \"Invalid attempt to bind an instance of \"\n              + instance.getClass().getName()\n              + \" as a @JsonAdapter for \"\n              + type.toString()\n              + \". @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory,\"\n              + \" JsonSerializer or JsonDeserializer.\");\n    }\n\n    if (typeAdapter != null && nullSafe) {\n      typeAdapter = typeAdapter.nullSafe();\n    }\n\n    return typeAdapter;\n  }\n\n  @SuppressWarnings(\"ReferenceEquality\")\n  private static boolean areSameFactories(TypeAdapterFactory a, TypeAdapterFactory b) {\n    // Checks for reference equality, like it is done by `Gson.getDelegateAdapter`","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/google/gson/blob/310ac341f2f92a454b229bf21f70d2d18b2b6db7/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java#L130-L166","documentation":"JsonAdapterAnnotationTypeAdapterFactory constructs an instance of the class referenced by @JsonAdapter and checks it is one of TypeAdapter, TypeAdapterFactory, JsonSerializer, or JsonDeserializer. If the class implements none of these, it throws IllegalArgumentException at adapter-resolution time (when Gson first requests an adapter for the annotated type).","triggerScenarios":"Annotating a type or field with @JsonAdapter(MyClass.class) where MyClass does not extend TypeAdapter nor implement TypeAdapterFactory/JsonSerializer/JsonDeserializer. Fires the first time Gson serializes/deserializes the annotated type.","commonSituations":"Forgetting to extend TypeAdapter; pointing the annotation at a plain POJO or a data class; refactoring a class to no longer implement the expected interface without updating the annotation.","solutions":["Make the referenced class extend TypeAdapter<T> (or implement one of the other three interfaces).","Correct the annotation to reference the actual adapter class.","If the class must stay as-is, wrap it in a TypeAdapter implementation.","Add a unit test that does gson.toJson / fromJson on the annotated type to catch the failure early."],"exampleFix":"// before\n@JsonAdapter(PlainConfig::class)\ndata class User(val name: String)\nclass PlainConfig // implements nothing\n\n// after\nclass PlainConfig : TypeAdapter<User>() {\n  override fun write(out: JsonWriter, v: User) { out.value(v.name) }\n  override fun read(`in`: JsonReader): User = User(`in`.nextString())\n}","handlingStrategy":"validation","validationCode":"// Verify at startup that every @JsonAdapter value is a supported adapter type\nstatic void validateJsonAdapters(Class<?>... types) {\n  for (Class<?> c : types) {\n    JsonAdapter a = c.getAnnotation(JsonAdapter.class);\n    if (a == null) continue;\n    Class<?> v = a.value();\n    boolean ok = TypeAdapter.class.isAssignableFrom(v)\n        || TypeAdapterFactory.class.isAssignableFrom(v)\n        || JsonSerializer.class.isAssignableFrom(v)\n        || JsonDeserializer.class.isAssignableFrom(v);\n    if (!ok) throw new IllegalStateException(c + \" @JsonAdapter value \" + v + \" is not an adapter\");\n  }\n}","typeGuard":"static boolean isValidAdapterClass(Class<?> c) {\n  return TypeAdapter.class.isAssignableFrom(c)\n      || TypeAdapterFactory.class.isAssignableFrom(c)\n      || JsonSerializer.class.isAssignableFrom(c)\n      || JsonDeserializer.class.isAssignableFrom(c);\n}","tryCatchPattern":"try {\n  gson.fromJson(json, Annotated.class);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"@JsonAdapter value must be\")) {\n    // fix the referenced class to implement one of the four interfaces\n    log.error(\"Bad @JsonAdapter on {}: {}\", Annotated.class, e.getMessage());\n  }\n  throw e;\n}","preventionTips":["Always extend TypeAdapter or implement TypeAdapterFactory for @JsonAdapter classes.","Add a startup self-test that constructs a Gson and round-trips every annotated type.","Review @JsonAdapter targets during code review.","Keep adapter classes final and dedicated (single responsibility)."],"tags":["jsonadapter","annotation","type-adapter","configuration"],"backgroundTag":null,"analyzedSha":"310ac341f2f92a454b229bf21f70d2d18b2b6db7","analyzedAt":"2026-08-10T02:58:47.455Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}