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
- Do not read after close(); perform all reads before closing.
- If you need the data after close, materialize it into POJOs/strings first, then close.
- Track a closed flag in your wrapper and short-circuit reads.
- 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
- Materialize data into POJOs/strings before close.
- Never share a JsonReader across operations or threads.
- Scope close() at the outermost layer so nothing reads after.
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
- JsonWriter is closed.
- End of input
- Expected a long but was
- Expected an int but was
- Invalid nesting limit
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)