combining must, should, must_not, and filter correctly
The Bool Query is the central tool of the Elasticsearch Query DSL for combining multiple conditions logically. Anyone who understands must, should, must_not, and filter, and knows when scoring context applies versus filter context, builds search queries that are both precise and fast, instead of throwing all clauses into one bucket and hoping for good results.
Table of Contents
- 1. What the Bool Query actually does
- 2. must: mandatory conditions in scoring context
- 3. should: optional clauses and relevance boost
- 4. must_not: exclusion without a score contribution
- 5. filter: filter context and its cache
- 6. Steering minimum_should_match precisely
- 7. Nested Bool Queries for complex logic
- 8. Scoring context vs. filter context: performance
- 9. Common mistakes with the Bool Query
- 10. Summary
- 11. FAQ
1. What the Bool Query actually does
The Bool Query is the building block that combines multiple conditions into a single search request in Elasticsearch and OpenSearch. Instead of a single query like match or term, the Bool Query describes a combination of four clauses: must, should, must_not, and filter. Each of these four clauses has its own meaning, its own scoring behavior, and its own performance characteristics. Anyone who understands the Bool Query only as an "AND combination of several conditions" is giving up most of its expressive power.
In practice, the Bool Query shows up in nearly every non-trivial search request: a product search that combines a search term with a price filter and an availability flag can hardly be modeled cleanly without a Bool Query. The decisive advantage over a flat list of conditions is that the Bool Query distinguishes between conditions that should contribute to relevance and conditions that should only filter, without affecting the score. This distinction is the thread that runs through every section that follows.
Structurally, the Bool Query is itself a query, meaning it can be used anywhere Elasticsearch expects a query, including nested inside another Bool Query. This composability makes the Bool Query a universal tool for complex search logic, from simple two-clause combinations to deeply nested rule sets with dozens of conditions, as found in large e-commerce search systems or log analysis platforms.
2. must: mandatory conditions in scoring context
The must clause of the Bool Query defines conditions that have to be satisfied for a document to appear in the results, and that simultaneously contribute to the relevance score. Every query inside must runs in what is called scoring context: Elasticsearch calculates, for every document, how well it matches the given condition, and sums these partial scores into the document's overall score. A match query inside must therefore does not just contribute a binary "matches" or "does not match", it delivers a graduated relevance value.
That makes must the right choice for every condition where the quality of the match matters for ranking the results, typically the user's actual search text. When a customer searches for "waterproof running shoes", a product whose title contains exactly those words in that order should rank higher than a product that only has the terms scattered across its description. That is exactly what the Bool Query delivers with a match clause inside must.
Multiple must clauses in a Bool Query behave logically like an AND: all of them have to be satisfied. Unlike filter, however, every single clause flows into the score calculation, which makes must computationally more expensive than a plain filter condition. That is why only conditions that are actually relevance-relevant belong in the must clause of a Bool Query, not just any arbitrary restriction.
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "waterproof running shoes" } },
{ "match": { "description": "outdoor" } }
]
}
}
}
// Both clauses run in scoring context and both contribute
// to the final _score of every matching document
3. should: optional clauses and relevance boost
The should clause of the Bool Query describes conditions that do not have to be satisfied, but whose satisfaction raises a document's score. If a Bool Query consists solely of should clauses, at least one of them has to match by default for a document to end up in the results. As soon as at least one must or filter clause is present, however, all should clauses become optional: they only affect the score from then on, not whether a document matches at all.
This behavior turns should into a tool for soft boosting. A typical example: the mandatory search runs through must against the product title, while additional should clauses rank documents higher whose brand, category, or tags also match the search term. A product that satisfies both the title match and a brand match gets a higher score than one that only hits the title, without the brand match becoming a mandatory condition.
A common use case for the Bool Query is combining several should clauses with different boost values to weight different fields differently. In most search applications, a match in the title field should count more than a match in a long free-text description, and that exact weighting can be fine-tuned through should clauses with individual boosts.
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "running shoes" } }
],
"should": [
{ "match": { "brand": { "query": "nike", "boost": 2.0 } } },
{ "match": { "tags": "bestseller" } }
]
}
}
}
// should clauses here are optional (a must clause exists)
// but still raise the score when they match
4. must_not: exclusion without a score contribution
The must_not clause of the Bool Query excludes documents that satisfy a given condition. Unlike must and should, must_not always runs in filter context, never in scoring context. That means a must_not clause never contributes to the score, it only decides in a purely binary way whether a document stays in the result set or drops out. This property makes must_not computationally cheap and well cacheable.
Typical use cases for must_not in the Bool Query are exclusion lists: hiding products from a certain category, filtering out deactivated records, or explicitly excluding certain status codes in a log data search. It is important to understand that must_not is not a negation in the classic boolean sense with scoring impact, but a pure set operation: the result set is reduced by every document that matches the inner query.
A common mistake with the Bool Query: developers try to model complex exclusion logic with several must_not clauses, even though a single terms query with a list of values would be more performant and more readable. As a rule of thumb, must_not is excellent for clear, binary exclusion criteria, but becomes unwieldy as soon as more than three or four conditions are combined.
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "running shoes" } }
],
"must_not": [
{ "term": { "status": "discontinued" } },
{ "range": { "stock": { "lte": 0 } } }
]
}
}
}
// must_not always runs in filter context, no score contribution
// discontinued items and out-of-stock items are excluded outright
5. filter: filter context and its cache
The filter clause of the Bool Query is the most important clause for performance optimization. Like must_not, filter also runs exclusively in filter context: Elasticsearch only checks whether a document satisfies the condition, without computing a graduated score for it. The result is always binary, yes or no. Because no scoring needs to be calculated, filter clauses are significantly cheaper than equivalent must clauses, especially for range queries, term queries on keyword fields, and exact matches.
The second major advantage of filter in the Bool Query is caching. Elasticsearch caches frequently used filter clauses at the segment level in what is called the filter cache, so repeated requests with the same filter condition, such as {"term": {"category": "shoes"}}, do not need to be re-evaluated every time. For search requests with stable, recurring filters like category, price range, or availability, this cache makes a noticeable performance difference, especially under high search volume.
The rule of thumb for the Bool Query is therefore: everything that contributes to relevance ranking belongs in must or should, everything that only narrows the result set belongs in filter. A price range, a category membership, or a stock level greater than zero are classic candidates for filter, because no user expects a cheaper product to rank higher purely because of its price.
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "running shoes" } }
],
"filter": [
{ "term": { "category.keyword": "running" } },
{ "range": { "price": { "gte": 50, "lte": 150 } } },
{ "range": { "stock": { "gt": 0 } } }
]
}
}
}
// filter clauses contribute zero score, only reduce the result set
// they are cacheable and much cheaper than equivalent must clauses
6. Steering minimum_should_match precisely
The minimum_should_match parameter controls, inside the Bool Query, how many of the existing should clauses have to be satisfied at minimum. Without a must or filter clause, the implicit default value is 1, meaning at least one should clause has to match. This default can be explicitly overridden, either as a fixed number, as a percentage, or as a combination of both.
In practice, minimum_should_match is especially relevant when a Bool Query models a list of synonyms or alternative search terms as should clauses. With "minimum_should_match": "75%", three out of four alternative terms have to match, which weighs precision against recall: a value that is too low brings in too many irrelevant hits, a value that is too high excludes relevant documents that only narrowly fail to satisfy every criterion.
A common use case for minimum_should_match in the Bool Query is the multi-term search, where every search term entered by the user is modeled as its own should clause, instead of using a single match query with the entire search string. That allows finer control over how tolerant the search is toward missing terms than the built-in minimum_should_match option of a plain match query offers.
GET /products/_search
{
"query": {
"bool": {
"should": [
{ "match": { "tags": "waterproof" } },
{ "match": { "tags": "breathable" } },
{ "match": { "tags": "lightweight" } },
{ "match": { "tags": "cushioned" } }
],
"minimum_should_match": "75%"
}
}
}
// at least 3 of the 4 should clauses must match
// tunes precision against recall for synonym-style queries
7. Nested Bool Queries for complex logic
Because a Bool Query is itself a query, it can be nested again inside any of its four clauses with another Bool Query. This composability is the key to modeling logical expressions that go beyond a simple AND of all clauses, such as "category A AND (brand X OR brand Y)". A flat Bool Query cannot express this grouping, a nested Bool Query can.
In practice, this need arises frequently with faceted search featuring OR combination within a facet and AND combination between facets. When a user in a shop selects multiple brands within the brand facet, products should be shown that belong to at least one of the selected brands, while still satisfying every other active facet filter. This logic is modeled through an outer Bool Query with filter clauses, one of which is itself an inner Bool Query with should clauses.
With deeply nested Bool Queries, it pays to keep readability in mind: every nesting level should represent a clearly nameable logical group. More than three or four levels of nesting are usually a sign that the search logic should be reconsidered or split into several separate queries whose results are combined downstream.
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "running shoes" } }
],
"filter": [
{ "term": { "category.keyword": "running" } },
{
"bool": {
"should": [
{ "term": { "brand.keyword": "nike" } },
{ "term": { "brand.keyword": "adidas" } }
],
"minimum_should_match": 1
}
}
]
}
}
}
// outer bool: category AND (brand nike OR brand adidas)
// the nested bool query expresses the OR grouping cleanly
8. Scoring context vs. filter context: performance in detail
The difference between scoring context and filter context is the most important performance lever the Bool Query offers. In scoring context, meaning for must and should, Elasticsearch calculates a relevance value for every potential hit document using the BM25 algorithm. This calculation costs compute time per document and clause. In filter context, meaning for filter and must_not, this calculation is skipped entirely, which makes filter clauses noticeably faster than equivalent scoring clauses at the same data volume.
In addition to skipping the score calculation, filter clauses in the Bool Query benefit from the filter cache already mentioned, which holds frequently repeated bitset results per segment. A combination of a must clause for the actual full-text search term and several filter clauses for category, price, and availability is therefore not just semantically correct, it is also the most performant structure for a typical e-commerce search.
A real-world benchmark example: a Bool Query with five filter conditions, three of which were mistakenly modeled as must instead of filter, can be noticeably slower on large indices with several million documents than the same query with correctly assigned clauses, because Elasticsearch has to do extra computation for each of the three unnecessary scoring clauses, work that is never used for ranking the results anyway.
9. Common mistakes with the Bool Query
The most common mistake with the Bool Query is accidentally putting filter conditions into must instead of filter. The result is correct in content, but unnecessarily slow, because Elasticsearch calculates a score for every one of these clauses that is not needed in the end anyway. The second common mistake is assuming that should clauses remain automatically optional without an explicit minimum_should_match, even when no must or filter clause exists. In that case, the implicit default of 1 applies, which can lead to unexpectedly empty result sets.
| Clause | Context | Contributes to score | Typical use |
|---|---|---|---|
| must | Scoring | Yes | Mandatory search term with relevance |
| should | Scoring | Yes | Optional boost, soft preferences |
| filter | Filter | No | Category, price, availability |
| must_not | Filter | No | Exclusion lists, deactivated records |
A third mistake concerns nesting: if a should clause is used inside an inner Bool Query without a matching minimum_should_match set there, that inner Bool Query behaves according to the default rules again, which quickly leads to unexpected results in complex nested structures. A careful look at every nesting level of the Bool Query reliably prevents this class of mistake.
Mironsoft
Elasticsearch and OpenSearch search solutions for demanding data volumes
Search queries that are fast and precise at the same time?
We analyze existing Bool Queries, separate scoring context from filter context, and optimize your search logic for relevance and performance at once.
Query audit
Check existing Bool Queries for misplaced filter and scoring clauses
Relevance tuning
Align should clauses, boosts, and minimum_should_match with your catalog
Performance review
Check filter cache usage and simplify nested Bool Queries
10. Summary
The Bool Query is the central tool of the Elasticsearch Query DSL for modeling search logic across four clearly separated clauses. must and should run in scoring context and influence relevance ranking, filter and must_not run in filter context and only decide, in a binary way, on membership in the result set. This separation is not an academic nuance, it has a direct impact on result quality and response time of a search.
Anyone who consciously decides, for every new condition in a Bool Query, whether it should contribute to relevance or only narrow the result set avoids the most common performance traps. minimum_should_match gives fine-grained control over how strictly optional clauses are evaluated, and nested Bool Queries allow modeling complex AND-OR combinations that a flat query cannot express. The Bool Query therefore remains the right tool even as requirement complexity grows, as long as nesting depth stays in check.
Bool Query in detail, the essentials at a glance
must and should
Run in scoring context and contribute to the relevance score. Use for anything that should influence ranking.
filter and must_not
Run in filter context, no score, but cacheable and faster. Use for pure inclusion and exclusion criteria.
minimum_should_match
Controls how many should clauses have to match. Default value 1, as soon as no must or filter clause exists.
Nesting
A Bool Query is itself a query and can be nested again in any clause, for complex AND-OR logic.