google/gson · error · IllegalStateException

JsonReader is closed

Error message

JsonReader is closed

What it means

Thrown by JsonReader.doPeek() when the top of the scope stack is JsonScope.CLOSED (JsonReader.java:673-675). After close() is called the reader sets stack[0]=CLOSED, so any subsequent read attempt (peek, hasNext, next*, begin*/end*) routes through doPeek and fails with this IllegalStateException. It prevents reads against a released reader whose underlying stream is gone.

Solutions

  1. Do not read after close(); perform all reads before closing.
  2. If you need the data after close, materialize it into POJOs/strings first, then close.
  3. Track a closed flag in your wrapper and short-circuit reads.
  4. Avoid sharing a single JsonReader across operations; create a fresh one per stream.

Example fix

// before
try (JsonReader r = new JsonReader(reader)) {
  return r.peek();
}
// later caller calls nextString() on the closed reader

// after
String value;
try (JsonReader r = new JsonReader(reader)) {
  value = r.nextString();
}
return value;
Defensive patterns

Strategy: validation

Validate before calling

// Read everything needed before closing; do not read after close.
List<String> vals = new ArrayList<>();
reader.beginArray();
while (reader.hasNext()) vals.add(reader.nextString());
reader.endArray();
reader.close();
// use `vals` after close, not `reader`

Type guard

// JsonReader has no public isOpen(); track closure in a wrapper.
class SafeJsonReader {
  private final JsonReader r;
  private boolean closed;
  boolean isOpen() { return !closed; }
  void close() throws IOException { r.close(); closed = true; }
}

Try / catch

try {
  reader.nextString();
} catch (IllegalStateException e) {
  if (e.getMessage().contains("closed")) return null;
  throw e;
}

Prevention

When it happens

Trigger: Calling any read method after reader.close(); using a reader inside a try-with-resources and then reading in a finally block or a callback that runs after close; sharing a reader instance that another thread or method already closed.

Common situations: try-with-resources that closes the reader before a deferred read; helper methods that close the reader and return a value the caller then tries to extend; reusing a reader field across multiple parse operations.

Related errors


AI-assisted analysis of google/gson@310ac341f2 (2026-08-10). Data as JSON: /api/errors/b066dcbdcf8b1544. Report an issue: GitHub.

Appendix: source

Thrown at gson/src/main/java/com/google/gson/stream/JsonReader.java:674

        default:
          throw syntaxError("Expected ':'");
      }
    } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
      if (strictness == Strictness.LENIENT) {
        consumeNonExecutePrefix();
      }
      stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
    } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
      int c = nextNonWhitespace(false);
      if (c == -1) {
        peeked = PEEKED_EOF;
        return peeked;
      } else {
        checkLenient();
        pos--;
      }
    } else if (peekStack == JsonScope.CLOSED) {
      throw new IllegalStateException("JsonReader is closed");
    }

    int c = nextNonWhitespace(true);
    switch (c) {
      case ']':
        if (peekStack == JsonScope.EMPTY_ARRAY) {
          peeked = PEEKED_END_ARRAY;
          return peeked;
        }
      // fall-through to handle ",]"
      case ';':
      case ',':
        // In lenient mode, a 0-length literal in an array means 'null'.
        if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) {
          checkLenient();
          pos--;
          peeked = PEEKED_NULL;
          return peeked;

View on GitHub (pinned to 310ac341f2)