{"record":{"id":"3fa85fc99bd10932","repo":"elastic/elasticsearch","slug":"unclosed-object-or-array-found","errorCode":null,"errorMessage":"Unclosed object or array found","messagePattern":"Unclosed object or array found","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/JsonXContentGenerator.java","lineNumber":610,"sourceCode":"        flush();\n        writer.accept(os);\n        flush();\n        writeEndRaw();\n    }\n\n    @Override\n    public void flush() throws IOException {\n        generator.flush();\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (generator.isClosed()) {\n            return;\n        }\n        JsonStreamContext context = generator.getOutputContext();\n        if ((context != null) && (context.inRoot() == false)) {\n            throw new IOException(\"Unclosed object or array found\");\n        }\n        if (writeLineFeedAtEnd) {\n            flush();\n            // Bypass generator to always write the line feed\n            getLowLevelGenerator().writeRaw(LF);\n        }\n        generator.close();\n    }\n\n    @Override\n    public boolean isClosed() {\n        return generator.isClosed();\n    }\n}\n","sourceCodeStart":592,"sourceCodeEnd":625,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/JsonXContentGenerator.java#L592-L625","documentation":"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.","triggerScenarios":"Calling generator.close() (or the try-with-resources exit) while one or more writeStartObject()/writeStartArray() calls were not matched by writeEndObject()/writeEndArray().","commonSituations":"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.","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"],"exampleFix":"// before\ngenerator.writeStartObject();\ngenerator.writeStartArray(\"items\");\ngenerator.writeEndArray();\n// missing writeEndObject()\ngenerator.close(); // throws\n// after\ngenerator.writeStartObject();\ngenerator.writeStartArray(\"items\");\ngenerator.writeEndArray();\ngenerator.writeEndObject();\ngenerator.close();","handlingStrategy":"try-catch","validationCode":"// structural pairing helper\ntry (var ignored = new JsonBlock(generator)) {\n    generator.writeStartObject();\n    // ...\n} // close calls writeEndObject if not yet closed","typeGuard":"// no language-level type guard; track open depth in a counter","tryCatchPattern":"try (JsonXContentGenerator g = ...) {\n    g.writeStartObject();\n    ...\n} catch (IOException e) {\n    // 'Unclosed object or array found' on close\n}","preventionTips":["Always pair start/end in the same lexical scope","Use helper abstractions that close on exit","Test generators against round-trip parse"],"tags":["x-content","json","serialization","structural"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}