google/gson · error · IllegalStateException

JsonReader is closed

Error message

JsonReader is closed

What it means

JsonTreeReader.peek throws IllegalStateException('JsonReader is closed') when the only item on the stack is the SENTINEL_CLOSED marker, i.e. close() was previously called on this JsonTreeReader. After close(), the reader replaces its stack with the sentinel and any further peek/next/hasNext call hits this guard.

Source

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

    } else if (o instanceof JsonObject) {
      return JsonToken.BEGIN_OBJECT;
    } else if (o instanceof JsonArray) {
      return JsonToken.BEGIN_ARRAY;
    } else if (o instanceof JsonPrimitive) {
      JsonPrimitive primitive = (JsonPrimitive) o;
      if (primitive.isString()) {
        return JsonToken.STRING;
      } else if (primitive.isBoolean()) {
        return JsonToken.BOOLEAN;
      } else if (primitive.isNumber()) {
        return JsonToken.NUMBER;
      } else {
        throw new AssertionError();
      }
    } else if (o instanceof JsonNull) {
      return JsonToken.NULL;
    } else if (o == SENTINEL_CLOSED) {
      throw new IllegalStateException("JsonReader is closed");
    } else {
      throw new MalformedJsonException(
          "Custom JsonElement subclass " + o.getClass().getName() + " is not supported");
    }
  }

  private Object peekStack() {
    return stack[stackSize - 1];
  }

  @CanIgnoreReturnValue
  private Object popStack() {
    Object result = stack[--stackSize];
    stack[stackSize] = null;
    return result;
  }

  private void expect(JsonToken expected) throws IOException {

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Do not use a JsonReader after calling close(); create a fresh JsonReader for each parse.
  2. Do not put JsonReader in try-with-resources if you intend to inspect it afterwards — close only when fully done.
  3. Audit custom TypeAdapters that accept a JsonReader to ensure they don't close it (Gson owns the lifecycle).
  4. If a reader must be reused, re-create it from the original JsonElement/string each time.

Example fix

// before
JsonReader r = new JsonTreeReader(jsonElement);
r.close();
r.peek(); // IllegalStateException: JsonReader is closed

// after
JsonReader r = new JsonTreeReader(jsonElement);
JsonElement e = JsonParser.parseReader(r); // use, then discard
r = new JsonTreeReader(jsonElement); // recreate if needed
Defensive patterns

Strategy: validation

Validate before calling

// Track open/closed state of readers you hand out
AtomicBoolean open = new AtomicBoolean(true);
JsonReader r = new JsonTreeReader(element);
// later, before use:
if (!open.get()) throw new IllegalStateException("Reader already closed");
r.peek();

Try / catch

try {
  reader.peek();
} catch (IllegalStateException e) {
  if ("JsonReader is closed".equals(e.getMessage())) {
    // recreate the reader or surface a clear error
    reader = new JsonTreeReader(originalElement);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling any read operation on a JsonTreeReader (constructed from a JsonElement) after invoking close() on it. Common in try-with-resources blocks where the JsonReader is auto-closed but reused afterwards.

Common situations: Wrapping JsonParser.parseReader in try-with-resources and then trying to use the reader; utility methods that close a JsonReader and the caller re-uses it; thread-handoff where one side closes and another reads.

Related errors


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