{"id":"fd667ac78f32ed3a","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/8b8628c65699bc4421696183c62ae0c1b9b281dc/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java#L130-L166","documentation":"JsonAdapterAnnotationTypeAdapterFactory throws IllegalArgumentException when the class referenced by @JsonAdapter is instantiated but is none of TypeAdapter, TypeAdapterFactory, JsonSerializer, or JsonDeserializer. Gson supports only those four binding types as the value of the annotation; anything else (or a class that implements none of them) is a programming error detected at adapter-binding time. The {className} is the adapter class, {type} is the type being bound.","triggerScenarios":"Annotating a type or field with @JsonAdapter(MyX.class) where MyX does not implement any of the four supported interfaces. Often MyX is a helper/DTO, an old-style adapter, or a class intended for a different JSON library.","commonSituations":"Copy/paste from a tutorial for a different library; renaming a class so it no longer implements TypeAdapter; annotating with an interface instead of the concrete adapter; leftover annotation after refactor.","solutions":["Make the referenced class implement exactly one of TypeAdapter, TypeAdapterFactory, JsonSerializer, or JsonDeserializer (and implement the required method).","If you meant to reference a factory that builds adapters, implement TypeAdapterFactory and override create(Gson, TypeToken).","Remove the @JsonAdapter annotation if no custom adapter is needed.","Rebuild/redeploy — this is detected at first adapter lookup so a stale class file can also cause it."],"exampleFix":"// before\n@JsonAdapter(MyDto.class) // MyDto is a plain DTO, not an adapter\nclass MyDto { String s; }\n\n// after\n@JsonAdapter(MyDtoAdapter.class)\nclass MyDto { String s; }\nclass MyDtoAdapter extends TypeAdapter<MyDto> {\n  @Override public MyDto read(JsonReader in) throws IOException { /* ... */ }\n  @Override public void write(JsonWriter out, MyDto v) throws IOException { /* ... */ }\n}","handlingStrategy":"type-guard","validationCode":"// Verify the @JsonAdapter value implements a supported interface at registration time\nClass<?> c = annotation.value();\nif (!TypeAdapter.class.isAssignableFrom(c)\n    && !TypeAdapterFactory.class.isAssignableFrom(c)\n    && !JsonSerializer.class.isAssignableFrom(c)\n    && !JsonDeserializer.class.isAssignableFrom(c)) {\n  throw new IllegalArgumentException(c.getName() + \" is not a valid @JsonAdapter value\");\n}","typeGuard":"static boolean isValidJsonAdapterValue(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  return gson.getAdapter(MyType.class);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"@JsonAdapter value must be\")) {\n    throw new ConfigurationException(\"Invalid @JsonAdapter on \" + MyType.class, e);\n  }\n  throw e;\n}","preventionTips":["Make every @JsonAdapter value class implement one of the four supported interfaces.","Add a unit test that loads all @JsonAdapter-annotated types and checks the value class.","Review annotations during code review for the correct adapter type."],"tags":["jsonadapter","annotation","type-adapter","configuration"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}