{"record":{"id":"43242a2591cdec1f","repo":"elastic/elasticsearch","slug":"parser-for-did-not-end-on","errorCode":null,"errorMessage":"parser for [{}] did not end on {}","messagePattern":"parser for \\[(.+?)\\] did not end on (.+?)","errorType":"exception","errorClass":"XContentParseException","httpStatus":null,"severity":"error","filePath":"libs/x-content/src/main/java/org/elasticsearch/xcontent/ObjectParser.java","lineNumber":672,"sourceCode":"                 * for having a cheap test.\n                 */\n                if (parser.currentToken() != XContentParser.Token.END_ARRAY) {\n                    throwMustEndOn(parser, currentFieldName, XContentParser.Token.END_ARRAY);\n                }\n            }\n            case END_OBJECT, END_ARRAY, FIELD_NAME -> throw throwUnexpectedToken(parser, token);\n            case VALUE_STRING, VALUE_NUMBER, VALUE_BOOLEAN, VALUE_EMBEDDED_OBJECT, VALUE_NULL -> parseValue(\n                parser,\n                fieldParser,\n                currentFieldName,\n                value,\n                context\n            );\n        }\n    }\n\n    private static void throwMustEndOn(XContentParser parser, String currentFieldName, XContentParser.Token token) {\n        throw new XContentParseException(parser.getTokenLocation(), \"parser for [\" + currentFieldName + \"] did not end on \" + token);\n    }\n\n    private XContentParseException throwUnexpectedToken(XContentParser parser, XContentParser.Token token) {\n        return new XContentParseException(parser.getTokenLocation(), \"[\" + name + \"]\" + token + \" is unexpected\");\n    }\n\n    private class FieldParser {\n        private final Parser<Value, Context> parser;\n        private final EnumSet<XContentParser.Token> supportedTokens;\n        private final ParseField parseField;\n        private final ValueType type;\n\n        FieldParser(Parser<Value, Context> parser, EnumSet<XContentParser.Token> supportedTokens, ParseField parseField, ValueType type) {\n            this.parser = parser;\n            this.supportedTokens = supportedTokens;\n            this.parseField = parseField;\n            this.type = type;\n        }","sourceCodeStart":654,"sourceCodeEnd":690,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/x-content/src/main/java/org/elasticsearch/xcontent/ObjectParser.java#L654-L690","documentation":"Thrown by throwMustEndOn when a field parser has finished consuming its content but the parser's current token is not the expected end token (END_OBJECT or END_ARRAY). This indicates the field's sub-parser read too few or too many tokens, leaving the parser in an inconsistent state relative to the expected token.","triggerScenarios":"A custom Parser callback that does not fully consume the sub-structure (e.g., stops reading before END_OBJECT). A declared field whose parser reads a nested object but leaves the parser positioned mid-object. A mismatch between the declared ValueType (which determines expected token boundaries) and the actual content structure.","commonSituations":"Custom ObjectParser extension with a Parser callback that doesn't consume all tokens. Declaring a field as a single value but providing a multi-value structure. Corruption in the input that causes the tokenizer to produce unexpected token sequences.","solutions":["If you wrote a custom Parser callback, ensure it consumes exactly the right number of tokens (up to and including the matching END token).","Verify the declared ValueType matches the actual JSON structure being parsed.","Check the request body for structural issues like missing closing braces or extra nested objects.","Use a JSON validator to ensure the structure is well-formed."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before sending, validate JSON structure is well-formed with matching braces\npublic static boolean isWellFormed(String json) {\n    int depth = 0;\n    boolean inString = false;\n    for (int i = 0; i < json.length(); i++) {\n        char c = json.charAt(i);\n        if (c == '\"') inString = !inString;\n        if (!inString) {\n            if (c == '{' || c == '[') depth++;\n            if (c == '}' || c == ']') depth--;\n        }\n    }\n    return depth == 0;\n}","typeGuard":null,"tryCatchPattern":"try {\n    objectParser.parse(parser, context);\n} catch (XContentParseException e) {\n    if (e.getMessage().contains(\"did not end on\")) {\n        logger.error(\"Parser state mismatch: {}\", e.getMessage());\n        return badRequest(\"Malformed content structure\");\n    }\n    throw e;\n}","preventionTips":["If writing custom Parser callbacks, test them thoroughly to ensure correct token consumption.","Validate JSON with a parser/validator before feeding it to ObjectParser.","Match declared ValueType to the actual JSON structure to avoid token boundary mismatches."],"tags":["parsing","xcontent","token-mismatch","object-parser"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}