elastic/elasticsearch · error · IllegalArgumentException

Unknown key for a VALUE_STRING in [project_routing]

Error message

Unknown key for a VALUE_STRING in [project_routing]

What it means

Thrown by RestMultiSearchTemplateAction.parseRequest() when project_routing appears in the per-request body (the second line of each request pair in _msearch/template NDJSON) instead of as a top-level URL query parameter. Because the parser is shared between _msearch/template and _search/template, project_routing in the body gets parsed into SearchTemplateRequest.getProjectRouting(). The handler explicitly checks this and rejects it, since for _msearch/template, project_routing must be a single URL-level parameter.

Source

Thrown at modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java:115

            (searchRequest, bytes) -> {
                SearchTemplateRequest searchTemplateRequest = SearchTemplateRequest.fromXContent(bytes);
                /*
                 * For multisearch requests, project_routing could appear within the request body as:
                 * {"project_routing": ...}
                 * {"id": ...}
                 *
                 * In such cases, it is picked up by MultiSearchRequest#readMultiLineFormat() and is associated with the
                 * SearchRequest object that represents the corresponding msearch request. However, it could also erroneously
                 * appear as:
                 * {...}
                 * {"project_routing": ..., "id": ...}
                 *
                 * This is because, the same parser is shared between _msearch/template and _search/template and the above
                 * format is valid only for the latter. For this reason, we need to explicitly check if project_routing got
                 * associated with the SearchTemplateRequest instead of SearchRequest and error out if needed.
                 */
                if (searchTemplateRequest.getProjectRouting() != null) {
                    throw new IllegalArgumentException("Unknown key for a VALUE_STRING in [project_routing]");
                }
                if (searchTemplateRequest.getScript() != null) {
                    searchTemplateRequest.setRequest(searchRequest);
                    multiRequest.add(searchTemplateRequest);
                } else {
                    throw new IllegalArgumentException("Malformed search template");
                }
                RestSearchAction.validateSearchRequest(restRequest, searchRequest);
            },
            (k, v, r) -> false,
            Optional.of(crossProjectEnabled),
            multiRequest.getProjectRouting()
        );
        return multiRequest;
    }

    @Override
    public boolean mediaTypesValid(RestRequest request) {

View on GitHub (pinned to db6a809a66)

Solutions

  1. Move project_routing to the URL query string: POST /_msearch/template?project_routing=<value>
  2. Remove project_routing from each per-request body line in the NDJSON payload
  3. Ensure the NDJSON body lines contain only template-specific fields (id, params, etc.), not routing parameters
  4. Note: this differs from _search/template where project_routing in the body IS valid

Example fix

// before — project_routing in the body of _msearch/template:
POST /_msearch/template
{}
{"project_routing": "my-project", "id": "my-template", "params": {}}

// after — project_routing in the URL:
POST /_msearch/template?project_routing=my-project
{}
{"id": "my-template", "params": {}}
Defensive patterns

Strategy: validation

Validate before calling

// Validate _msearch/template body does not contain project_routing in per-request lines
// project_routing should only be in the URL query parameter
String url = "/_msearch/template";
if (projectRouting != null) {
    url += "?project_routing=" + URLEncoder.encode(projectRouting, StandardCharsets.UTF_8);
}
// body lines should NOT include project_routing:
// header: {"index": "my-index"}
// body:   {"id": "my-template", "params": {...}}

Prevention

When it happens

Trigger: Sending a _msearch/template request where one of the per-request body objects contains {"project_routing": "value", "id": "template_id"}. In _msearch/template, each request pair is (header_line, body_line). If project_routing is in the body_line, it gets parsed into the SearchTemplateRequest rather than the SearchRequest, and this check fires. The correct location is ?project_routing=value in the URL.

Common situations: Copying the _search/template body format (where project_routing in the body is valid) into a _msearch/template request. Confusing the per-request body format with the top-level parameter format. Cross-project Serverless mode where project_routing is relevant.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/af9f836ea2285370. Report an issue: GitHub.