elastic/elasticsearch · error · IOException
Unclosed object or array found
Error message
Unclosed object or array found
What it means
On close(), JsonXContentGenerator inspects the Jackson output context. If the context is not in the root state (i.e. there is an unclosed object or array), it throws IOException. This enforces structural completeness before the underlying generator is closed, so a half-written document fails loudly rather than producing malformed JSON.
Source
Thrown at libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/JsonXContentGenerator.java:610
flush();
writer.accept(os);
flush();
writeEndRaw();
}
@Override
public void flush() throws IOException {
generator.flush();
}
@Override
public void close() throws IOException {
if (generator.isClosed()) {
return;
}
JsonStreamContext context = generator.getOutputContext();
if ((context != null) && (context.inRoot() == false)) {
throw new IOException("Unclosed object or array found");
}
if (writeLineFeedAtEnd) {
flush();
// Bypass generator to always write the line feed
getLowLevelGenerator().writeRaw(LF);
}
generator.close();
}
@Override
public boolean isClosed() {
return generator.isClosed();
}
}
View on GitHub (pinned to db6a809a66)
Solutions
- Always pair writeStartObject/writeStartArray with the matching writeEndObject/writeEndArray in finally blocks or use try-with-resources around the structure
- Use a stack-based or block-scoped helper that guarantees the end call
- Re-run with a JSON validator to find the unbalanced structure
Example fix
// before
generator.writeStartObject();
generator.writeStartArray("items");
generator.writeEndArray();
// missing writeEndObject()
generator.close(); // throws
// after
generator.writeStartObject();
generator.writeStartArray("items");
generator.writeEndArray();
generator.writeEndObject();
generator.close(); Defensive patterns
Strategy: try-catch
Validate before calling
// structural pairing helper
try (var ignored = new JsonBlock(generator)) {
generator.writeStartObject();
// ...
} // close calls writeEndObject if not yet closed Type guard
// no language-level type guard; track open depth in a counter
Try / catch
try (JsonXContentGenerator g = ...) {
g.writeStartObject();
...
} catch (IOException e) {
// 'Unclosed object or array found' on close
} Prevention
- Always pair start/end in the same lexical scope
- Use helper abstractions that close on exit
- Test generators against round-trip parse
When it happens
Trigger: Calling generator.close() (or the try-with-resources exit) while one or more writeStartObject()/writeStartArray() calls were not matched by writeEndObject()/writeEndArray().
Common situations: A branch returned or threw before the matching end call; an exception during writing was swallowed and close() ran on an unbalanced context; refactoring added a new startObject but missed the end; conditional writes that start a structure on one path but not the other.
Related errors
- Can't write raw bytes whose xcontent-type can't be guessed
- Unexpected end of file
- Expected text at {} but found {}
- Invalid block quote starting at ${start} in: ${body}
- Invalid json in {name}. The error is: {errorMessage}. After
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/3fa85fc99bd10932.
Report an issue: GitHub.