Elasticsearch Bool Query: Syntax, Parameters, and Examples

The Elasticsearch bool query composes other queries using four clause types: must (AND, scored), filter (AND, unscored and cacheable), should (OR, scored), and must_not (NOT, unscored). It is the standard way to express compound logic in the query DSL. Scores from must and should clauses are summed; filter and must_not clauses contribute nothing to _score and are eligible for the per-node query cache.

Syntax

GET /index/_search
{
  "query": {
    "bool": {
      "must":     [ /* scored AND clauses */ ],
      "filter":   [ /* unscored AND clauses */ ],
      "should":   [ /* scored OR clauses */ ],
      "must_not": [ /* unscored NOT clauses */ ],
      "minimum_should_match": 1,
      "boost": 1.0
    }
  }
}

Parameters

Parameter Description Required Default
must Array of queries that must match. Scored. No []
filter Array of queries that must match. Unscored, cacheable. No []
should Array of queries that should match. Scored. No []
must_not Array of queries that must not match. Unscored, cacheable. No []
minimum_should_match Minimum should clauses that must match. Number or percentage. No 1 if the query has only should clauses (no must/filter), otherwise 0
boost Score multiplier for the whole bool. No 1.0

The cluster-wide cap indices.query.bool.max_clause_count defaults to 4096 on Elasticsearch 8.x and later (raised from 1024 in 7.x). Hitting the cap raises too_many_clauses.

Examples

Typical search: keyword match plus filters:

GET /articles/_search
{
  "query": {
    "bool": {
      "must":   [ { "match": { "title": "elasticsearch" } } ],
      "filter": [
        { "term":  { "status": "published" } },
        { "range": { "published_at": { "gte": "now-30d/d" } } }
      ],
      "must_not": [ { "term": { "category": "news" } } ]
    }
  }
}

should clauses as soft boosters - they add score but are not required:

GET /articles/_search
{
  "query": {
    "bool": {
      "must":   [ { "match": { "title": "elasticsearch" } } ],
      "should": [
        { "term": { "tags": "performance" } },
        { "term": { "tags": "tuning" } }
      ]
    }
  }
}

should as OR with minimum_should_match:

GET /articles/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":   "elasticsearch" } },
        { "match": { "summary": "elasticsearch" } },
        { "match": { "content": "elasticsearch" } }
      ],
      "minimum_should_match": 2
    }
  }
}

Nested bool for grouped logic (A AND (B OR C)):

GET /articles/_search
{
  "query": {
    "bool": {
      "must": [ { "term": { "lang": "en" } } ],
      "filter": [
        {
          "bool": {
            "should": [
              { "term": { "tags": "search" } },
              { "term": { "tags": "indexing" } }
            ],
            "minimum_should_match": 1
          }
        }
      ]
    }
  }
}

Performance and Use Notes

The single most impactful optimization is moving non-scoring predicates from must to filter. filter and must_not clauses skip BM25 scoring and are cached at the segment level, so a repeat filter (e.g. status:published, tenant_id:42) runs from cache after the first request. The clauses themselves are not deduplicated across requests by key alone; structurally identical clauses cache.

should-only queries default to minimum_should_match: 1. When must or filter is present, should defaults to 0 and acts purely as a scorer; setting minimum_should_match then makes individual should clauses required. Deeply nested bools are correct but harder to read - prefer two or three levels and rewrite further nesting as a custom query.

Inefficient bool composition is the dominant cause of slow Elasticsearch searches. Walking slow logs to find bool queries that put exact-match predicates in must instead of filter, or that approach the 4096-clause cap, is exactly the optimization loop NeverBlink runs continuously.

Common Mistakes

  1. Putting exact-match terms (status, tenant, date ranges) in must. They should live in filter to skip scoring and enable caching.
  2. Forgetting that adding a must or filter clause changes the default minimum_should_match from 1 to 0, silently making should clauses optional.
  3. Building bool queries with thousands of should term clauses and hitting too_many_clauses (indices.query.bool.max_clause_count, default 4096 in 8.x). Use a terms query or terms lookup instead.
  4. Nesting bools more than two or three levels deep, which both costs CPU and obscures the intent.
  5. Expecting must_not to score - it does not. Its only role is to exclude documents.

NeverBlink is an AI DBA for Elasticsearch and OpenSearch that continuously profiles production query traffic. For bool queries specifically, NeverBlink:

  • Identifies bool queries placing exact-match predicates (status flags, tenant IDs, date ranges) inside must instead of filter, defeating the per-node query cache and forcing BM25 scoring on every clause
  • Flags bool queries approaching or hitting indices.query.bool.max_clause_count (default 4096 on 8.x), surfacing too_many_clauses failures and the services issuing them
  • Spots deeply nested bools (4+ levels) where intent is obscured and rewrite cost spikes, plus accidental minimum_should_match: 0 cases where adding a must clause silently turned should into a pure scorer
  • Traces each slow bool query back to the calling service via slow-log and APM correlation
  • Recommends concrete rewrites: move predicates from must to filter, collapse large should term lists into a terms query or terms lookup, flatten unnecessary nesting, and explicitly set minimum_should_match when the default changes
  • Tracks cache hit-rate and latency improvement after the rewrite

This converts the manual slow-log plus DSL-review loop into a continuous optimization workflow.

Try NeverBlink on your cluster.

Frequently Asked Questions

Q: What is the difference between must and filter in a bool query?
A: must and filter both require matching, but filter is executed in filter context: scores are skipped and results are eligible for the per-node query cache. Use filter for any clause where you do not need a relevance score, e.g. status flags, date ranges, tenant IDs.

Q: How does minimum_should_match work in a bool query?
A: minimum_should_match controls how many should clauses must match. It defaults to 1 if the bool has only should clauses, and to 0 otherwise. It accepts an absolute count (2), a percentage (75%), or a combination expression.

Q: Can I nest a bool query inside another bool query?
A: Yes. Nesting is the standard pattern for expressing grouped logic like A AND (B OR C). Keep nesting shallow (two or three levels) so the query is debuggable and the rewrite stays cheap.

Q: How does scoring work in a bool query?
A: Scores from matching must and should clauses are summed, then multiplied by a coordination factor and the top-level boost. filter and must_not contribute nothing to _score. Adding more matching should clauses raises the score.

Q: What is the maximum number of clauses in a bool query?
A: The cluster setting indices.query.bool.max_clause_count caps the total clauses Lucene will materialize. The default is 4096 on Elasticsearch 8.x (up from 1024 in 7.x). For very large lists prefer a terms query or a terms lookup.

Q: Should I use bool to combine filters with aggregations?
A: Yes - filters in the bool's filter clause prune documents before aggregation, which is usually what you want. For independent per-bucket filters, use filters or filter aggregations on top of the bool.

Q: What is the best tool to find inefficient bool queries in production Elasticsearch?
A: NeverBlink profiles Elasticsearch and OpenSearch slow logs, flags bool queries misusing must for unscored predicates, those approaching indices.query.bool.max_clause_count, and those with surprising minimum_should_match defaults, attributes each one to the calling service, and recommends concrete filter-context and terms query rewrites.

Subscribe to the NeverBlink Newsletter

Get early access to new NeverBlink features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.