{"record":{"id":"bb6e2929f89973b8","repo":"elastic/elasticsearch","slug":"failed-to-parse-list-expecting-xcontentparser","errorCode":null,"errorMessage":"Failed to parse list:  expecting ${XContentParser.Token.START_ARRAY} but got ${token}","messagePattern":"Failed to parse list:  expecting (.+?) but got (.+?)","errorType":"exception","errorClass":"XContentParseException","httpStatus":null,"severity":"error","filePath":"libs/x-content/src/main/java/org/elasticsearch/xcontent/support/AbstractXContentParser.java","lineNumber":451,"sourceCode":"        }\n        if (token == XContentParser.Token.START_OBJECT) {\n            return parser.nextFieldName();\n        }\n        return token == Token.FIELD_NAME ? parser.currentName() : null;\n    }\n\n    // Skips the current parser to the next array start. Assumes that the parser is either positioned before an array field's name token or\n    // on the start array token.\n    private static void skipToListStart(XContentParser parser) throws IOException {\n        Token token = parser.currentToken();\n        if (token == null) {\n            token = parser.nextToken();\n        }\n        if (token == XContentParser.Token.FIELD_NAME) {\n            token = parser.nextToken();\n        }\n        if (token != XContentParser.Token.START_ARRAY) {\n            throw new XContentParseException(\n                parser.getTokenLocation(),\n                \"Failed to parse list:  expecting \" + XContentParser.Token.START_ARRAY + \" but got \" + token\n            );\n        }\n    }\n\n    // read a list without bounds checks, assuming the current parser is always on an array start\n    private static List<Object> readListUnsafe(XContentParser parser, Supplier<Map<String, Object>> mapFactory) throws IOException {\n        assert parser.currentToken() == Token.START_ARRAY;\n        ArrayList<Object> list = new ArrayList<>();\n        for (Token token = parser.nextToken(); token != null && token != XContentParser.Token.END_ARRAY; token = parser.nextToken()) {\n            list.add(readValueUnsafe(token, parser, mapFactory));\n        }\n        return list;\n    }\n\n    public static Object readValue(XContentParser parser, Supplier<Map<String, Object>> mapFactory) throws IOException {\n        return readValueUnsafe(parser.currentToken(), parser, mapFactory);","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/x-content/src/main/java/org/elasticsearch/xcontent/support/AbstractXContentParser.java#L433-L469","documentation":"Thrown by AbstractXContentParser.skipToListStart when the parser is asked to read a list (via list() or listOrderedMap()) but, after skipping an optional FIELD_NAME token, the current token is not START_ARRAY. The parser expected the JSON position to be at (or just before) an array but found a scalar, object, or end token instead.","triggerScenarios":"Calling XContentParser.list() while positioned on a VALUE_STRING, VALUE_NUMBER, START_OBJECT, END_OBJECT, or null token. Reached when application code assumes a field is an array but the JSON contains a single scalar value, or when parsing logic advances the parser incorrectly before the list read.","commonSituations":"Documents where a field is sometimes a single value and sometimes an array (JSON's single-vs-array ambiguity). Hand-written XContent parsing code that mishandles token positioning. Custom ingest/rest handlers reading configuration that was serialized as an object instead of an array. Migration of mappings where the field shape changed.","solutions":["Normalize the producer to always emit the field as an array (even single-element arrays).","In manual parser code, check currentToken() before calling list() and handle VALUE_*/START_OBJECT branches explicitly.","Use the parser's `ensureExpectedToken` or token-aware helpers to guard list reads.","If you do not control the producer, pre-process the JSON to wrap scalars in arrays."],"exampleFix":"// before: list() called on a scalar token\nToken t = parser.currentToken();\nList<Object> items = parser.list();\n// after: guard the token\nToken t = parser.currentToken();\nif (t != Token.START_ARRAY) { throw new IllegalArgumentException(\"expected array at \" + parser.getTokenLocation()); }\nList<Object> items = parser.list();","handlingStrategy":"type-guard","validationCode":"if (parser.currentToken() != XContentParser.Token.START_ARRAY) {\n  // materialize a single value as a one-element list, or fail fast\n  throw new IllegalStateException(\"expected array, got \" + parser.currentToken());\n}","typeGuard":"boolean isAtArrayStart(XContentParser p) {\n  XContentParser.Token t = p.currentToken();\n  if (t == XContentParser.Token.FIELD_NAME) {\n    try { return p.nextToken() == XContentParser.Token.START_ARRAY; }\n    catch (IOException e) { return false; }\n  }\n  return t == XContentParser.Token.START_ARRAY;\n}","tryCatchPattern":null,"preventionTips":["Always check currentToken() before calling list()/listOrderedMap().","Normalize producers to always emit arrays for fields parsed as lists.","Use ensureExpectedToken helpers from the parser API."],"tags":["xcontent","parsing","list","array","token"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}