google/gson · error · ClassCastException

Type adapter '{typeAdapter}' returned wrong type; requested

Error message

Type adapter '{typeAdapter}' returned wrong type; requested {typeOfT.getRawType()} but got instance of {object.getClass()}
Verify that the adapter was registered for the correct type.

What it means

Thrown by Gson.fromJson(JsonReader, TypeToken) after the type adapter's read() returns, when the returned object is non-null but is not an instance of the requested type (after boxing primitives). This means a custom TypeAdapter returned the wrong class, breaking the TypeAdapter<T> contract. It is a ClassCastException, surfaced directly (not wrapped) from fromJson.

Source

Thrown at gson/src/main/java/com/google/gson/Gson.java:1106

      throws JsonIOException, JsonSyntaxException {
    boolean isEmpty = true;
    Strictness oldStrictness = reader.getStrictness();

    if (this.strictness != null) {
      reader.setStrictness(this.strictness);
    } else if (reader.getStrictness() == Strictness.LEGACY_STRICT) {
      // For backward compatibility change to LENIENT if reader has default strictness LEGACY_STRICT
      reader.setStrictness(Strictness.LENIENT);
    }

    try {
      JsonToken unused = reader.peek();
      isEmpty = false;
      TypeAdapter<T> typeAdapter = getAdapter(typeOfT);
      T object = typeAdapter.read(reader);
      Class<?> expectedTypeWrapped = Primitives.wrap(typeOfT.getRawType());
      if (object != null && !expectedTypeWrapped.isInstance(object)) {
        throw new ClassCastException(
            "Type adapter '"
                + typeAdapter
                + "' returned wrong type; requested "
                + typeOfT.getRawType()
                + " but got instance of "
                + object.getClass()
                + "\nVerify that the adapter was registered for the correct type.");
      }
      return object;
    } catch (EOFException e) {
      /*
       * For compatibility with JSON 1.5 and earlier, we return null for empty
       * documents instead of throwing.
       */
      if (isEmpty) {
        return null;
      }
      throw new JsonSyntaxException(e);

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Verify the TypeAdapter is registered for the exact type it produces (e.g. register it under Date.class only if read() returns a Date).
  2. Make read() return the declared T; convert internally before returning.
  3. If using generics, use TypeToken to capture the parameterized type and ensure the adapter's output matches.
  4. Add a unit test asserting the returned object's class equals the requested type.

Example fix

// before: adapter registered for Date returns a String
class BadDateAdapter extends TypeAdapter<Date> {
  public Date read(JsonReader r) throws IOException { return (Date)(Object)r.nextString(); } // wrong
}

// after: convert to the declared return type
class GoodDateAdapter extends TypeAdapter<Date> {
  public Date read(JsonReader r) throws IOException { return new Date(r.nextLong()); }
  public void write(JsonWriter w, Date v) throws IOException { w.value(v.getTime()); }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// After a custom adapter read, verify the returned type matches
TypeAdapter<Date> adapter = ...;
Date result = adapter.read(reader);
if (result != null && !(result instanceof Date)) {
  throw new IllegalStateException("Adapter returned " + result.getClass());
}

Type guard

static <T> boolean returnsCorrectType(TypeAdapter<T> adapter, JsonReader r, Class<T> expected)
    throws IOException {
  T v = adapter.read(r);
  return v == null || expected.isInstance(v);
}

Try / catch

try {
  Foo f = gson.fromJson(json, TypeToken.get(Foo.class));
} catch (ClassCastException e) {
  if (e.getMessage().contains("returned wrong type")) {
    // an adapter is registered for the wrong type; fix the registration
  } else throw e;
}

Prevention

When it happens

Trigger: A custom TypeAdapter<Date> whose read() returns a String or Long; a TypeAdapter registered for type A but internally producing B; raw-type misuse where a TypeAdapter is registered with a raw class but produces a generic-substituted type; unsafe casts inside an adapter leaking an incompatible object.

Common situations: Adapters copied from examples targeting a different type than the one registered; generics erasure hiding a mismatch at compile time; an adapter that delegates to another adapter returning a broader type; @JsonAdapter pointing at an adapter written for a different class; refactoring a model without updating its adapter.

Related errors


AI-assisted analysis of google/gson@8b8628c656 (2026-08-04). Data as JSON: /data/errors/561455962f6f967f.json. Report an issue: GitHub.