google/gson · error · IllegalStateException

Unexpected token: {peeked}

Error message

Unexpected token: {peeked}

What it means

JsonElementTypeAdapter.readTerminal throws IllegalStateException('Unexpected token: ' + peeked) when the reader is positioned at a structural token (BEGIN_ARRAY, BEGIN_OBJECT, END_*, NAME, END_DOCUMENT) at a point where only a terminal value (STRING, NUMBER, BOOLEAN, NULL) is expected. This signals the JsonReader is in an invalid state for reading a single JsonElement value, typically because the caller advanced the reader incorrectly before delegating to JsonElementTypeAdapter. The {peeked} is the offending JsonToken.

Source

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

    }
  }

  /** Reads a {@link JsonElement} which cannot have any nested elements */
  private JsonElement readTerminal(JsonReader in, JsonToken peeked) throws IOException {
    switch (peeked) {
      case STRING:
        return new JsonPrimitive(in.nextString());
      case NUMBER:
        String number = in.nextString();
        return new JsonPrimitive(new LazilyParsedNumber(number));
      case BOOLEAN:
        return new JsonPrimitive(in.nextBoolean());
      case NULL:
        in.nextNull();
        return JsonNull.INSTANCE;
      default:
        // When read(JsonReader) is called with JsonReader in invalid state
        throw new IllegalStateException("Unexpected token: " + peeked);
    }
  }

  @Override
  public JsonElement read(JsonReader in) throws IOException {
    // Optimization if value already exists as JsonElement
    if (in instanceof JsonTreeReader) {
      return ((JsonTreeReader) in).nextJsonElement();
    }

    // Either JsonArray or JsonObject
    JsonElement current;
    JsonToken peeked = in.peek();

    current = tryBeginNesting(in, peeked);
    if (current == null) {
      return readTerminal(in, peeked);
    }

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Ensure the JsonReader is positioned at the start of a value (STRING/NUMBER/BOOLEAN/NULL/BEGIN_ARRAY/BEGIN_OBJECT) before reading a JsonElement.
  2. Do not interleave manual beginObject()/nextName() with JsonElement reads on the same reader — let one consumer own the cursor.
  3. Create a fresh JsonReader for each parse, or rewind/recreate the source if you must re-read.
  4. In a custom adapter, call in.peek() and handle structural tokens explicitly instead of delegating blindly.

Example fix

// before
JsonReader r = new JsonReader(new StringReader("{\"a\":1}"));
r.beginObject(); r.nextName();
JsonElement e = JsonParser.parseReader(r); // positioned at NUMBER inside object -> IllegalStateException

// after: parse the whole document, then navigate the tree
JsonElement e = JsonParser.parseString("{\"a\":1}");
int a = e.getAsJsonObject().get("a").getAsInt();
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the reader is positioned at a value token before reading a JsonElement
JsonToken t = reader.peek();
if (t == JsonToken.NAME || t == JsonToken.END_ARRAY
    || t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT
    || t == JsonToken.BEGIN_ARRAY || t == JsonToken.BEGIN_OBJECT) {
  // BEGIN_ARRAY/BEGIN_OBJECT are fine for JsonElement; NAME/END_*/END_DOCUMENT are not
}
if (t == JsonToken.NAME || t == JsonToken.END_ARRAY
    || t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT) {
  throw new IllegalStateException("Reader not at a value: " + t);
}

Try / catch

try {
  return JsonParser.parseReader(reader);
} catch (IllegalStateException e) {
  if (e.getMessage() != null && e.getMessage().startsWith("Unexpected token: ")) {
    throw new IllegalStateException("Reader misaligned before JsonElement read at " + reader.getPath(), e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling JsonParser.parseReader(JsonReader) (or reading a JsonElement) after manually consuming part of the stream so the cursor sits on a NAME or END token; reusing a JsonReader past its logical end; mixing manual nextName()/beginObject() with JsonElement parsing on the same reader.

Common situations: Custom TypeAdapter that does some manual token reading then calls gson.getAdapter(JsonElement.class).read(in) at the wrong position; streaming parser reuse; tests that hand a JsonReader to multiple consumers.

Related errors


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