google/gson · error · IllegalStateException

Unexpected {peeked} when reading a JsonElement.

Error message

Unexpected {peeked} when reading a JsonElement.

What it means

Thrown by JsonTreeReader.nextJsonElement() (an internal bridge called when a TypeAdapter/TypeAdapterFactory requests the current element as a JsonElement tree) when the reader is parked on a structural token that is not a value. Only NAME, END_ARRAY, END_OBJECT, and END_DOCUMENT are rejected; BEGIN_ARRAY, BEGIN_OBJECT, and all scalars are allowed. It signals that the caller asked for a complete element while the reader is mid-traversal of a container or at end of input.

Source

Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java:317

    try {
      result = primitive.getAsInt();
    } catch (NumberFormatException e) {
      throw numberFormatException("Expected an int but was " + primitive.getAsString(), e);
    }
    popStack();
    if (stackSize > 0) {
      pathIndices[stackSize - 1]++;
    }
    return result;
  }

  JsonElement nextJsonElement() throws IOException {
    JsonToken peeked = peek();
    if (peeked == JsonToken.NAME
        || peeked == JsonToken.END_ARRAY
        || peeked == JsonToken.END_OBJECT
        || peeked == JsonToken.END_DOCUMENT) {
      throw new IllegalStateException("Unexpected " + peeked + " when reading a JsonElement.");
    }
    JsonElement element = (JsonElement) peekStack();
    skipValue();
    return element;
  }

  @Override
  public void close() throws IOException {
    stack = new Object[] {SENTINEL_CLOSED};
    stackSize = 1;
  }

  @Override
  public void skipValue() throws IOException {
    JsonToken peeked = peek();
    switch (peeked) {
      case NAME:
        @SuppressWarnings("unused")

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Call nextName() (or beginObject()/beginArray()) to advance the reader off the NAME token before requesting the element as JsonElement.
  2. Guard with a peek() check: only call the JsonElement-reading path when peek() is BEGIN_OBJECT, BEGIN_ARRAY, STRING, NUMBER, BOOLEAN, or NULL.
  3. Ensure the reader has not already been exhausted (stackSize==0 -> END_DOCUMENT) before calling read; check hasNext() in your loop.
  4. If you wrote a custom adapter, restructure so you consume NAME tokens with nextName() and then read the value, rather than grabbing the whole element mid-object.

Example fix

// before: reader is on NAME, calling element read throws
if (reader.peek() == JsonToken.NAME) {
  reader.nextName(); // consume the name first
}
JsonElement el = gson.fromJson(reader, JsonElement.class);

// after: only read element when positioned on a value token
JsonToken t = reader.peek();
if (t == JsonToken.NAME || t == JsonToken.END_ARRAY
    || t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT) {
  throw new IllegalStateException("not at a value: " + t);
}
JsonElement el = gson.fromJson(reader, JsonElement.class);
Defensive patterns

Strategy: validation

Validate before calling

// Validate reader position before requesting a JsonElement
JsonToken t = reader.peek();
if (t == JsonToken.NAME || t == JsonToken.END_ARRAY
    || t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT) {
  throw new IllegalStateException("Reader not at a value token: " + t);
}
JsonElement el = gson.fromJson(reader, JsonElement.class);

Type guard

// Guard: only true when safe to read a value element
static boolean atValueToken(JsonReader r) throws IOException {
  JsonToken t = r.peek();
  return t == JsonToken.BEGIN_OBJECT || t == JsonToken.BEGIN_ARRAY
      || t == JsonToken.STRING || t == JsonToken.NUMBER
      || t == JsonToken.BOOLEAN || t == JsonToken.NULL;
}

Try / catch

try {
  JsonElement el = gson.fromJson(reader, JsonElement.class);
} catch (IllegalStateException e) {
  if (e.getMessage() != null && e.getMessage().startsWith("Unexpected ")) {
    // reader was mid-container or at EOF; advance and recover
    reader.skipValue();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling a TypeAdapter that internally invokes JsonReader-based nextJsonElement() while positioned on a NAME token (i.e. expecting a value but the cursor advanced only to the key), or after an array/object has been fully consumed (END_ARRAY/END_OBJECT), or at EOF (END_DOCUMENT). Most commonly triggered by custom adapters calling gson.fromJson(..., JsonElement.class) on a sub-tree, or by adapters that call peek()/skipValue() in the wrong order before requesting the element.

Common situations: Writing a custom TypeAdapterFactory whose read() peeks then conditionally delegates, but the peek advanced the cursor to a NAME; mixing hasNext()/nextName() with code that tries to grab the whole entry as a JsonElement; deserializing into Object or JsonElement from an already partially-consumed reader; bugs in streaming adapters that forget to call nextName() before reading the value.

Related errors


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