How to Use Term Queries in Elasticsearch
Term queries are one of the most precise and performant query types in Elasticsearch. They are designed for exact-value matching and are commonly used on structured fields such as IDs, keywords, status flags, or enums. Unlike full-text queries, term queries do not analyze the input text, which makes them ideal when we need strict equality checks rather than relevance-based scoring.
In this article, we explore how term queries work and how to use term queries across different layers, including the Elasticsearch Query DSL, the official Java client, Spring Data Elasticsearch, and aggregations.
1. What Is a Term Query?
A term query searches for documents that contain an exact value in a specific field. Elasticsearch compares the provided query value directly against the indexed term stored in the inverted index. No tokenization, stemming, or lowercasing is applied at query time. Because of this behavior, term queries are best suited for keyword, numeric, boolean, and date fields rather than analyzed text fields.
Configuration
Before using term queries, we need a properly configured Elasticsearch instance. Correct setup ensures that mappings, queries, and client integrations behave as expected.
Elasticsearch Index Configuration
PUT /orders
{
"mappings": {
"properties": {
"orderId": {
"type": "keyword"
},
"customerId": {
"type": "keyword"
},
"status": {
"type": "keyword"
},
"region": {
"type": "keyword"
},
"amount": {
"type": "double"
},
"paid": {
"type": "boolean"
}
}
}
}
This mapping explicitly defines fields such as orderId, status, and region as keyword types. This ensures that Elasticsearch indexes these values as single, unanalyzed terms, which is required for reliable term queries.
Indexing Sample Data
To demonstrate term queries, we need data stored in Elasticsearch. These documents represent orders with structured attributes suitable for exact matching.
POST /orders/_doc/1
{
"orderId": "O-1001",
"customerId": "C-01",
"status": "SHIPPED",
"region": "EU",
"amount": 250.00,
"paid": true
}
POST /orders/_doc/2
{
"orderId": "O-1002",
"customerId": "C-02",
"status": "PENDING",
"region": "US",
"amount": 180.00,
"paid": false
}
Each field is indexed exactly as provided. Since no analysis is applied, term queries must use the same values and casing to return results.
2. Term Query with Elasticsearch Query DSL
The Elasticsearch Query DSL provides a JSON-based language for defining queries. A term query in the DSL is used to match documents containing a specific exact value in a field.
Basic Term Query
curl -X GET "http://localhost:9200/orders/_search?pretty" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"status": {
"value": "SHIPPED"
}
}
}
}'
Multiple Term Queries in a Bool Filter
In practical applications, search conditions often involve multiple exact constraints, such as finding all paid orders in the EU region that have already been shipped, which is why combining multiple term queries inside a bool filter becomes essential.
curl -X GET "http://localhost:9200/orders/_search?pretty" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"filter": [
{
"term": {
"status": "SHIPPED"
}
},
{
"term": {
"region": "EU"
}
},
{
"term": {
"paid": true
}
}
]
}
}
}'
In this query, each term query acts as a strict condition. Because they are placed inside the filter context, Elasticsearch skips scoring and focuses purely on matching documents. This makes the query both faster and cache-friendly, which is ideal for frequently executed filters.
3. Term Query With the Elasticsearch Java Client
The Elasticsearch Java client provides a type-safe and modern way to interact with Elasticsearch using Java. Using this client, we can construct term queries programmatically while benefiting from compile-time safety and clear APIs. To use the Java client, we first add the Maven dependencies, configure the connection settings, and define an Elasticsearch configuration class.
Maven pom.xml
Spring Data Elasticsearch provides seamless integration between Spring applications and Elasticsearch. Adding this dependency enables repository support, template operations, and access to Elasticsearch query APIs.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>9.2.2</version>
</dependency>
This dependency pulls in the required Elasticsearch client libraries that are compatible with Spring Data.
The Elasticsearch Java client and Jackson dependencies are transitively provided by spring-boot-starter-data-elasticsearch.
application.properties Configuration
The connection details for Elasticsearch should be externalized into configuration files. This enables easy switching between environments, such as development, testing, and production.
elasticsearch.host=localhost elasticsearch.port=9200 elasticsearch.scheme=http
These properties define where the Elasticsearch cluster is running and how the application connects to it.
Elasticsearch Configuration Class
A configuration class is used to initialize and expose the ElasticsearchClient as a Spring-managed bean. This allows the client to be injected and reused across the application.
@Configuration
public class ElasticsearchConfig {
@Value("${elasticsearch.host}")
private String host;
@Value("${elasticsearch.port}")
private int port;
@Value("${elasticsearch.scheme}")
private String scheme;
@Bean
public ElasticsearchClient elasticsearchLowLevelClient() {
RestClient restClient = RestClient.builder(new HttpHost(host, port, scheme)).build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
This class creates a low-level RestClient, wraps it with a transport layer, and exposes an ElasticsearchClient bean.
Executing a Term Query Using the Java Client
Once the client is configured, we can construct and execute a term query programmatically.
@Service
public class OrderSearchService {
private final ElasticsearchClient elasticsearchClient;
public OrderSearchService(ElasticsearchClient elasticsearchClient) {
this.elasticsearchClient = elasticsearchClient;
}
public SearchResponse<Order> findShippedOrders() throws Exception {
TermQuery termQuery = TermQuery.of(t -> t
.field("status")
.value("SHIPPED")
);
SearchRequest request = SearchRequest.of(s -> s
.index("orders")
.query(q -> q.term(termQuery))
);
return elasticsearchClient.search(request, Order.class);
}
}
In this example, a term query is created to match orders with a status of SHIPPED. The query is added to a SearchRequest, which is then executed using the injected ElasticsearchClient. The response is automatically mapped to the Order domain object, making the result easy to work with in application logic. The Order model is shown below.
@Document(indexName = "orders")
public class Order {
@Id
private String orderId;
@Field(type = FieldType.Keyword)
private String customerId;
@Field(type = FieldType.Keyword)
private String status;
@Field(type = FieldType.Keyword)
private String region;
@Field(type = FieldType.Double)
private double amount;
@Field(type = FieldType.Boolean)
private boolean paid;
public Order() {
}
public Order(String orderId, String customerId, String status, String region, double amount, boolean paid) {
this.orderId = orderId;
this.customerId = customerId;
this.status = status;
this.region = region;
this.amount = amount;
this.paid = paid;
}
// Getters and Setters
}
Using a Bool Filter With Multiple Term Queries
In practice, searches often require multiple exact conditions. Elasticsearch handles this efficiently by combining multiple term queries within a bool query using the filter clause.
public SearchResponse<Order> findFilteredOrders() throws Exception {
SearchRequest request = SearchRequest.of(s -> s
.index("orders")
.query(q -> q.bool(b -> b
.filter(f -> f.term(t -> t
.field("status")
.value("SHIPPED")))
.filter(f -> f.term(t -> t
.field("region")
.value("EU")))
.filter(f -> f.term(t -> t
.field("paid")
.value(true))))));
SearchResponse<Order> response
= elasticsearchClient.search(request, Order.class);
logger.info("Filtered orders search executed");
logger.info("Total hits: {}", response.hits().total().value());
response.hits().hits().forEach(hit
-> logger.info("Order found: {}", hit.source())
);
return response;
}
This implementation uses a bool query with multiple filter clauses, each containing a term query. The filter context is ideal here because it does not affect scoring and is optimized for exact matches. Each filter narrows down the result set by enforcing a strict condition on the status, region, and paid fields, resulting in fast and predictable query performance.
4. Term Query With Spring Data Elasticsearch
While using the Elasticsearch Java Client provides full control over query construction, Spring Data Elasticsearch offers a higher-level abstraction that simplifies common search operations. By using repositories, we can express the same query logic declaratively, eliminating the need to manually build search requests.
Repository Interface
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
List<Order> findByStatusAndRegionAndPaid(String status, String region, boolean paid);
}
Service Using the Repository
@Service
public class OrderSearchService2 {
private final OrderRepository orderRepository;
public OrderSearchService2(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public List<Order> findFilteredOrders() {
return orderRepository.findByStatusAndRegionAndPaid(
"SHIPPED",
"EU",
true
);
}
}
Spring Data Elasticsearch automatically translates the method name findByStatusAndRegionAndPaid into a bool query with multiple term filters under the hood. This approach removes the need for manual query construction while still producing efficient Elasticsearch queries. It is ideal for simple and well-defined search conditions, while the Java Client remains better suited for complex or dynamic queries.
5. Conclusion
In this article, we explored how to use Elasticsearch term queries to perform efficient exact-match searches, starting from basic single-field queries to more advanced scenarios involving multiple term queries combined within a bool filter. We also demonstrated how to implement these queries using the Elasticsearch Java Client and how to simplify the same logic using Spring Data Elasticsearch repositories. Together, these approaches provide a foundation for building performant search functionality in our applications.
6. Download the Source Code
This article explored term queries in Elasticsearch.
You can download the full source code of this example here: elasticsearch term queries




