elastic/elasticsearch · error · IllegalArgumentException

Malformed search template

Error message

Malformed search template

What it means

Thrown by the _msearch/template REST handler when a per-line sub-request was parsed but its SearchTemplateRequest carries no script (neither inline 'source' nor stored 'id'). The parser populated the request object yet left getScript() null, meaning the body section did not contain a usable template reference. Elasticsearch treats this as a malformed request because every template sub-request must resolve to exactly one template.

Source

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

                 *
                 * 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) {
        return super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType());
    }

    @Override
    protected Set<String> responseParams() {
        return RESPONSE_PARAMS;

View on GitHub (pinned to db6a809a66)

Solutions

  1. Inspect the NDJSON: every even-indexed (0-based) line is a header, every odd-indexed line must be a JSON object containing either an inline 'source' template or a stored 'id', plus optional 'params'.
  2. Add the missing template field: {"id":"my_template","params":{...}} or {"source":{"query":{"match_all":{}}},"params":{}}.
  3. Validate the request body with a single _search/template call first to confirm the template resolves, then wrap it in the _msearch/template line-pair format.
  4. Check for stray blank lines or trailing newlines in the NDJSON body that shift header/body alignment.

Example fix

// before (broken: body line has no template)
{ "index": "movies" }
{ "params": { "q": "star" } }
// after
{ "index": "movies" }
{ "id": "movie_search", "params": { "q": "star" } }
Defensive patterns

Strategy: validation

Validate before calling

// Before sending _msearch/template, verify each body line has a template id or source
function validateMsearchTemplate(ndjson) {
  const lines = ndjson.trim().split(/\n/);
  if (lines.length % 2 !== 0) throw new Error('Expected even number of NDJSON lines (header/body pairs)');
  for (let i = 1; i < lines.length; i += 2) {
    const body = JSON.parse(lines[i]);
    if (!body.id && !body.source) {
      throw new Error(`Body line ${i} missing 'id' or 'source' (would cause 'Malformed search template')`);
    }
  }
  return true;
}

Prevention

When it happens

Trigger: POSTing to _msearch/template with a request line/index header followed by a body line that omits both the inline 'source' object and the stored 'id' string (e.g. a body line that is just `{"params":{}}` or `{}`). Also triggered by a body line containing only params/explain/profile keys with no template identifier.

Common situations: Copy-pasting a _search/template body into _msearch/template and forgetting that each pair needs the template key. Truncating a multi-line NDJSON payload. Misnesting the 'id'/'source' field inside 'params' by mistake. Sending a header line where the body line was expected (shifting all pairs by one).

Understand the failure class

Related errors


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