google/gson · error · IllegalArgumentException

Couldn't write {class}

Error message

Couldn't write {class}

What it means

JsonElementTypeAdapter.write throws IllegalArgumentException('Couldn't write ' + value.getClass()) when the JsonElement passed for serialization is not null/jsonNull, not a primitive, not an array, and not an object. The built-in writer only knows how to emit those four shapes; a user-defined JsonElement subclass falls into the final else branch. The {class} is the unsupported JsonElement subclass.

Source

Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonElementTypeAdapter.java:168

      }

    } else if (value.isJsonArray()) {
      out.beginArray();
      for (JsonElement e : value.getAsJsonArray()) {
        write(out, e);
      }
      out.endArray();

    } else if (value.isJsonObject()) {
      out.beginObject();
      for (Map.Entry<String, JsonElement> e : value.getAsJsonObject().entrySet()) {
        out.name(e.getKey());
        write(out, e.getValue());
      }
      out.endObject();

    } else {
      throw new IllegalArgumentException("Couldn't write " + value.getClass());
    }
  }
}

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Avoid subclassing JsonElement; favor composition (hold a JsonObject field) instead of inheritance.
  2. If you must subclass, ensure you do not override isJsonObject()/isJsonArray()/isJsonPrimitive()/isJsonNull() to return false for your type — better, convert to a standard JsonObject before serializing.
  3. Before calling toJson, normalize: JsonObject std = custom.toStandardJsonObject(); gson.toJson(std).
  4. If you control the writer path, register a custom TypeAdapter<JsonElement> that handles your subclass.

Example fix

// before
class TaggedObject extends JsonObject { String tag; } // isJsonObject() may still be true, but...
// if a subclass breaks isJsonObject() -> IllegalArgumentException('Couldn't write ' + class)

// after: compose, don't subclass
class TaggedObject { String tag; JsonObject payload = new JsonObject(); }
gson.toJson(tagged); // serialize the composed JsonObject via its own adapter
Defensive patterns

Strategy: type-guard

Validate before calling

// Reject unsupported JsonElement subclasses before serializing
if (!(element instanceof JsonObject) && !(element instanceof JsonArray)
    && !(element instanceof JsonPrimitive) && !(element instanceof JsonNull)) {
  throw new IllegalArgumentException("Unsupported JsonElement: " + element.getClass());
}

Type guard

static boolean isWritableJsonElement(JsonElement e) {
  return e == null || e.isJsonNull() || e.isJsonObject()
      || e.isJsonArray() || e.isJsonPrimitive();
}

Try / catch

try {
  gson.toJson(element);
} catch (IllegalArgumentException e) {
  if (e.getMessage() != null && e.getMessage().startsWith("Couldn't write ")) {
    throw new IllegalArgumentException("Unsupported JsonElement subclass; convert to JsonObject", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: A user subclasses JsonElement (or JsonObject/JsonArray) and overrides identity in a way that makes isJsonObject()/isJsonArray()/isJsonPrimitive()/isJsonNull() all return false, then passes the instance to Gson.toJson / Streams.write / JsonElementTypeAdapter.write.

Common situations: Custom JSON tree types intended to add metadata; defensive subclasses that break instanceof checks; libraries that extend JsonObject to add ordering/typing and forget to keep isJsonObject() true.

Related errors


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