# How to Combine a Range Query Elasticsearch with Boolean Should and Must Clauses

> Master range query Elasticsearch with boolean logic. Combine must and should clauses for precise filtering and optional criteria. Learn effective techniques now.

- Repository: [elastic/elasticsearch](https://github.com/elastic/elasticsearch)
- Tags: how-to-guide
- Published: 2026-02-20

---

**You can combine a range query elasticsearch with boolean logic by placing mandatory filters in the `must` clause and optional range conditions in the `should` clause, using `minimum_should_match` to enforce at least one optional condition when needed.**

The `elastic/elasticsearch` repository provides robust builder classes that translate these high-level query definitions into optimized Lucene `BooleanQuery` objects. Understanding how `BoolQueryBuilder` and `RangeQueryBuilder` interact allows you to construct precise search criteria that balance mandatory filtering with flexible, score-boosting range conditions.

## Understanding the Bool Query Structure

Elasticsearch’s boolean query framework, implemented in [`BoolQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/BoolQueryBuilder.java), organizes sub-queries into four distinct buckets. Each bucket maps to a specific Lucene `Occur` enum value during query execution.

- **`must`** – All clauses must match. These contribute to the final relevance score.
- **`filter`** – Must match but executes in filter context (no scoring, cacheable).
- **`should`** – Optional clauses that boost documents when matched. By default, if a `bool` query contains a `must` clause, `should` clauses are optional for matching but affect ranking.
- **`must_not`** – Excludes documents that match any clause in this bucket.

When `doToQuery` executes in `BoolQueryBuilder` (around lines 302–309), it iterates through `mustClauses` and `shouldClauses`, translating each into Lucene `BooleanQuery.Builder` occurrences of `MUST` and `SHOULD` respectively.

## Implementing Range Query Elasticsearch in Boolean Context

A **range query** (`RangeQueryBuilder`) defines bounds using `gt`, `gte`, `lt`, and `lte` parameters. According to the source in [`RangeQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/RangeQueryBuilder.java), this builder constructs a Lucene `PointRangeQuery` for numeric fields or a `TermRangeQuery` for keyword fields.

### JSON DSL Example

The following search request demonstrates combining a mandatory term filter with two optional range conditions. The `minimum_should_match: 1` parameter ensures that at least one range condition must be satisfied, effectively creating an OR relationship between the date and price ranges while maintaining the mandatory category filter.

```json
GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "category": "electronics" } }
      ],
      "should": [
        {
          "range": {
            "release_date": {
              "gte": "now-1y/d",
              "lt": "now/d"
            }
          }
        },
        {
          "range": {
            "price": {
              "gte": 100,
              "lte": 500
            }
          }
        }
      ],
      "minimum_should_match": 1
    }
  }
}

```

### Java High-Level REST Client Example

When using the Java API, factory methods in [`QueryBuilders.java`](https://github.com/elastic/elasticsearch/blob/main/QueryBuilders.java) instantiate the builder objects. The `BoolQueryBuilder` maintains internal `List<QueryBuilder>` instances for each clause type, allowing you to programmatically assemble complex logic.

```java
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.RangeQueryBuilder;

// Initialize bool query with mandatory category filter
BoolQueryBuilder bool = QueryBuilders.boolQuery()
        .must(QueryBuilders.termQuery("category", "electronics"));

// Construct optional range queries
RangeQueryBuilder recentRelease = QueryBuilders
        .rangeQuery("release_date")
        .gte("now-1y/d")
        .lt("now/d");

RangeQueryBuilder priceWindow = QueryBuilders
        .rangeQuery("price")
        .gte(100)
        .lte(500);

// Add to SHOULD bucket for optional matching
bool.should(recentRelease);
bool.should(priceWindow);

// Require at least one optional clause to match
bool.minimumShouldMatch(1);

```

During execution, `BoolQueryBuilder.doToQuery` (lines 302–309) translates these into a Lucene `BooleanQuery` where the `must` clause receives `Occur.MUST` and each `should` clause receives `Occur.SHOULD`.

## When to Use Should vs Filter for Range Queries

Choosing between `should` and `filter` for your range query elasticsearch logic depends on scoring requirements and query performance.

**Use `should` when:**
- You want documents matching the range to receive a **higher relevance score**.
- The range represents a **preference** rather than a hard requirement.
- You need `minimum_should_match` to enforce that at least one of several optional ranges matches.

**Use `filter` when:**
- You require the range as a **hard constraint** but don’t need it to affect scoring.
- You want **query caching** benefits, as filter contexts are cached automatically by Elasticsearch.
- You need **faster execution** for high-cardinality fields.

Example using `filter` instead of `should`:

```json
{
  "bool": {
    "must": [{ "term": { "category": "electronics" } }],
    "filter": [
      {
        "range": {
          "price": { "gte": 100, "lte": 500 }
        }
      }
    ]
  }
}

```

In the Java API, this corresponds to `bool.filter(priceWindow)` which adds the query to the `filterClauses` list (lines 56–57 in [`BoolQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/BoolQueryBuilder.java)), ultimately translated to `Occur.FILTER` in the Lucene query.

## Summary

- The **bool query** in [`BoolQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/BoolQueryBuilder.java) provides four buckets: `must`, `filter`, `should`, and `must_not`, each mapping to specific Lucene `Occur` enums.
- A **range query** (`RangeQueryBuilder`) defines bounds using `gt`, `gte`, `lt`, and `lte`, generating Lucene range queries for numeric or date fields.
- Combining `must` with `should` allows you to enforce mandatory filters while optionally boosting documents that match specific ranges.
- Use `minimum_should_match: 1` to require at least one optional clause, effectively creating an OR condition within your boolean logic.
- For pure filtering without scoring impact, use the `filter` bucket instead of `should` to leverage query caching and improved performance.

## Frequently Asked Questions

### What is the difference between must and should in an Elasticsearch bool query?

The `must` clause contains queries that **must** match for a document to be included in the results, and these queries contribute to the relevance score. The `should` clause contains optional queries that boost the score of matching documents; by default, if a `bool` query contains `must` clauses, the `should` clauses are not required for a match unless `minimum_should_match` is configured.

### How does minimum_should_match affect a range query in the should clause?

When you place a **range query** in the `should` clause, setting `minimum_should_match: 1` (or higher) transforms the optional clause into a required condition. This means at least one (or the specified number) of the range conditions must be satisfied for the document to match, effectively creating an OR relationship between multiple range queries while maintaining any mandatory `must` filters.

### Should I use should or filter for range queries in Elasticsearch?

Use **`should`** when you want documents matching the range to receive a higher relevance score or when the range represents a preference rather than a hard requirement. Use **`filter`** when the range is a mandatory constraint that should not affect scoring, as filter contexts are cached and generally execute faster for high-cardinality fields. In [`BoolQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/BoolQueryBuilder.java), `should` maps to `Occur.SHOULD` while `filter` maps to `Occur.FILTER`.

### What files in the Elasticsearch source code handle bool and range query construction?

The **bool query** logic is implemented in [`BoolQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/BoolQueryBuilder.java) located at [`server/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/server/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java), which manages the `mustClauses`, `shouldClauses`, `filterClauses`, and `mustNotClauses` lists. The **range query** implementation resides in [`RangeQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/RangeQueryBuilder.java) at [`server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java), handling the construction of Lucene range queries from DSL parameters like `gte` and `lte`. Factory methods for both are available in [`QueryBuilders.java`](https://github.com/elastic/elasticsearch/blob/main/QueryBuilders.java).