google/gson · error · IllegalArgumentException
Invalid attempt to bind an instance of {className} as a @Jso
Error message
Invalid attempt to bind an instance of {className} as a @JsonAdapter for {type}. @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory, JsonSerializer or JsonDeserializer. What it means
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.
Source
Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java:148
// Uses dummy factory instances because TreeTypeAdapter needs a 'skipPast' factory for
// `Gson.getDelegateAdapter` call and has to differentiate there whether TreeTypeAdapter was
// created for @JsonAdapter on class or field
TypeAdapterFactory skipPast;
if (isClassAnnotation) {
skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY;
} else {
skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY;
}
@SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapter<?> tempAdapter =
new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe);
typeAdapter = tempAdapter;
// TreeTypeAdapter handles nullSafe; don't additionally call `nullSafe()`
nullSafe = false;
} else {
throw new IllegalArgumentException(
"Invalid attempt to bind an instance of "
+ instance.getClass().getName()
+ " as a @JsonAdapter for "
+ type.toString()
+ ". @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory,"
+ " JsonSerializer or JsonDeserializer.");
}
if (typeAdapter != null && nullSafe) {
typeAdapter = typeAdapter.nullSafe();
}
return typeAdapter;
}
@SuppressWarnings("ReferenceEquality")
private static boolean areSameFactories(TypeAdapterFactory a, TypeAdapterFactory b) {
// Checks for reference equality, like it is done by `Gson.getDelegateAdapter`View on GitHub (pinned to 8b8628c656)
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.
Example fix
// before
@JsonAdapter(MyDto.class) // MyDto is a plain DTO, not an adapter
class MyDto { String s; }
// after
@JsonAdapter(MyDtoAdapter.class)
class MyDto { String s; }
class MyDtoAdapter extends TypeAdapter<MyDto> {
@Override public MyDto read(JsonReader in) throws IOException { /* ... */ }
@Override public void write(JsonWriter out, MyDto v) throws IOException { /* ... */ }
} Defensive patterns
Strategy: type-guard
Validate before calling
// Verify the @JsonAdapter value implements a supported interface at registration time
Class<?> c = annotation.value();
if (!TypeAdapter.class.isAssignableFrom(c)
&& !TypeAdapterFactory.class.isAssignableFrom(c)
&& !JsonSerializer.class.isAssignableFrom(c)
&& !JsonDeserializer.class.isAssignableFrom(c)) {
throw new IllegalArgumentException(c.getName() + " is not a valid @JsonAdapter value");
} Type guard
static boolean isValidJsonAdapterValue(Class<?> c) {
return TypeAdapter.class.isAssignableFrom(c)
|| TypeAdapterFactory.class.isAssignableFrom(c)
|| JsonSerializer.class.isAssignableFrom(c)
|| JsonDeserializer.class.isAssignableFrom(c);
} Try / catch
try {
return gson.getAdapter(MyType.class);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("@JsonAdapter value must be")) {
throw new ConfigurationException("Invalid @JsonAdapter on " + MyType.class, e);
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- GSON ({GsonBuildConfig.VERSION}) cannot handle {type}
- GSON cannot serialize or deserialize {type}
- Class {typeAdapter.getClass().getName()} does not implement
- Cannot override built-in adapter for {type}
- types and labels must be unique
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/fd667ac78f32ed3a.json.
Report an issue: GitHub.