Yes, we're now running our Black Friday Sale. All Access and Pro are 33% off until 2nd December, 2025:
Using TermQueries in Elastic Search
Last updated: November 26, 2025
1. Introduction
TermQuery is one of the core building blocks in Elasticsearch. It performs exact matches against fields without analysis, tokenization, or text transformations. That’s why it’s a good fit for structured data such as:
-
- keywords (tags, categories, roles)
- booleans
- numeric values
- identifiers (UUIDs, SKUs, user IDs)
In this article, we’ll focus on a common practical scenario that utilizes multiple term queries within a bool filter to construct search conditions.
2. Term Query with Elasticsearch Query DSL
We’ll start with direct queries to Elasticsearch using Query DSL. First, we’ll create the users index:
PUT /users
{
"mappings": {
"properties": {
"id": { "type": "keyword" },
"name": { "type": "text" },
"role": { "type": "keyword" },
"is_active": { "type": "boolean" }
}
}
}
Here, we’ve defined multiple fields for the user. We’ll use the role and is_active fields for term queries. Next, we’ll add multiple users to our index:
POST /users/_bulk
{ "index": { "_id": "1" } }
{ "id": "1", "name": "Alice", "role": "admin", "is_active": true }
{ "index": { "_id": "2" } }
{ "id": "2", "name": "Bob", "role": "user", "is_active": true }
{ "index": { "_id": "3" } }
{ "id": "3", "name": "Charlie", "role": "admin", "is_active": false }
{ "index": { "_id": "4" } }
{ "id": "4", "name": "Diana", "role": "manager", "is_active": true }
Finally, we’ll run a query with multiple term filters:
GET /users/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "role": "admin" } },
{ "term": { "is_active": true } }
]
}
}
}
We’ve used a Boolean instruction to combine several term filters with the AND condition. In the response, we can see that only one user matches the filters:
"hits": [
{
"_index": "users",
"_id": "1",
"_score": 0.0,
"_source": {
"id": "1",
"name": "Alice",
"role": "admin",
"is_active": true
}
}
]
With this combination, we get fast, cache-friendly results. However, we lose scoring and rely on exact matches.
3. Dependencies and Configuration
To set up the Elasticsearch Java Client and Spring Data Elasticsearch repository, let’s first add the spring-data-elasticsearch dependency:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>${spring-data-elasticsearch.version}</version>
</dependency>
Next, let’s add the Elasticsearch host and port info to our application.yml file:
elasticsearch:
hostAndPort: localhost:9200
Now, we’re ready to create the ElasticsearchConfiguration with all the required client:
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.baeldung.spring.data.es.termsqueries.repository")
public class ElasticsearchConfiguration extends AbstractElasticsearchConfiguration {
@Value("${elasticsearch.hostAndPort}")
private String hostAndPort;
@Bean
@Override
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(hostAndPort)
.build();
return RestClients.create(clientConfiguration).rest();
}
@Bean
public ElasticsearchClient elasticsearchLowLevelClient() {
RestClient restClient = RestClient.builder(HttpHost.create("http://" + hostAndPort))
.build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
Here, we’ve created a RestHighLevelClient bean for use in Spring Data repositories. We’ve also defined an ElasticsearchClient bean for low-level interactions. Additionally, we’ve specified the package for our Spring Data repositories. In both beans, we’ve used the Elasticsearch host and port from the properties file.
4. Term Query With the Elasticsearch Java Client
Now, let’s prepare a terms query to Elasticsearch using the ElasticsearchLowLevelClient bean:
@SpringBootTest
@ContextConfiguration(classes = ElasticsearchConfiguration.class)
public class ElasticSearchTermsQueriesManualTest {
@Autowired
private ElasticsearchClient elasticsearchLowLevelClient;
@Test
void givenAdminRoleAndActiveStatusFilter_whenSearch_thenReturnsOnlyActiveAdmins() throws Exception {
Query roleQuery = TermQuery.of(t -> t.field("role.keyword").value("admin"))._toQuery();
Query activeQuery = TermQuery.of(t -> t.field("is_active").value(true))._toQuery();
Query boolQuery = BoolQuery.of(b -> b.filter(roleQuery).filter(activeQuery))._toQuery();
SearchRequest request = SearchRequest.of(s -> s.index("users").query(boolQuery));
SearchResponse<Map> response = elasticsearchLowLevelClient.search(request, Map.class);
assertThat(response.hits().hits())
.hasSize(1)
.first()
.extracting(Hit::source)
.satisfies(source -> {
assertThat(source)
.isNotNull()
.values()
.containsExactly("1", "Alice", "admin", true);
});
}
}
We’ve used TermQuery for the role and is_active fields. Then, we’ve combined them with a BoolQuery and a filter. We mapped the response to the Map class. As expected, we retrieved only one match from the user index.
5. Term Query With Spring Data Elasticsearch
We can query our index using Spring Data repositories. With the proper mapping, we’ll get the same term query behavior. Let’s create a User model:
@Document(indexName = "users")
public class User {
@Id
private String id;
@Field(type = FieldType.Keyword, name = "role")
private String role;
@Field(type = FieldType.Text, name = "name")
private String name;
@Field(type = FieldType.Boolean, name = "is_active")
private Boolean isActive;
// Getters and setters
}
We’ve specified the index name and all field names and types to ensure proper mapping. Now, let’s create UserRepository:
public interface UserRepository extends ElasticsearchRepository<User, String> {
List<User> findByRoleAndIsActive(String role, boolean isActive);
}
We extend ElasticsearchRepository and add the findByRoleAndIsActive method to search the users by role and isActive. Finally, let’s call our repository:
@SpringBootTest
@ContextConfiguration(classes = ElasticsearchConfiguration.class)
public class ElasticSearchTermsQueriesManualTest {
@Autowired
private UserRepository userRepository;
@Test
void givenAdminRoleAndActiveStatusFilter_whenSearchUsingRepository_thenReturnsOnlyActiveAdmins() throws Exception {
List<User> users = userRepository.findByRoleAndIsActive("admin", true);
assertThat(users)
.hasSize(1)
.first()
.satisfies(user -> {
assertThat(user.getId()).isEqualTo("1");
assertThat(user.getName()).isEqualTo("Alice");
assertThat(user.getRole()).isEqualTo("admin");
assertThat(user.getIsActive()).isTrue();
});
}
}
As expected, we’ve retrieved only one user. Under the hood, Spring Data repository builds the same term query. We lose flexibility and control, but we’ve got a simple alternative without any implementation details.
6. Term Queries Inside Aggregations
We can combine term queries with aggregations to get analytical insights from filtered data. For example, let’s count how many users exist per role, but only for active users:
@SpringBootTest
@ContextConfiguration(classes = ElasticsearchConfiguration.class)
public class ElasticSearchTermsQueriesManualTest {
@Autowired
private ElasticsearchClient elasticsearchLowLevelClient;
@Test
void givenActiveUsers_whenAggregateByRole_thenReturnsRoleCounts() throws Exception {
Query activeQuery = TermQuery.of(t -> t.field("is_active").value(true))._toQuery();
Aggregation aggregation = Aggregation.of(a -> a
.terms(t -> t.field("role.keyword")));
SearchRequest request = SearchRequest.of(s -> s
.index("users")
.query(activeQuery)
.aggregations("by_role", aggregation));
SearchResponse<Void> response = elasticsearchLowLevelClient.search(request, Void.class);
StringTermsAggregate rolesAggregate = response.aggregations().get("by_role").sterms();
assertThat(rolesAggregate.buckets().array())
.extracting(b -> b.key().stringValue())
.containsExactlyInAnyOrder("admin", "user", "manager");
assertThat(rolesAggregate.buckets().array())
.extracting(MultiBucketBase::docCount)
.contains(1L, 1L, 1L);
}
}
We first filter active users using a term query on the is_active field. Then we aggregate them by the role.keyword field using a terms aggregation. This approach efficiently combines filtering and aggregation because both rely on the same inverted index lookups. Elasticsearch doesn’t need to scan all documents; it only counts matching terms in the filtered subset.
7. Conclusion
In this article, we’ve reviewed Elasticsearch Term Queries. We’ve explored different ways to use them, from direct DSL calls to Spring Data repositories. We’ve also reviewed their aggregation capabilities to analyze filtered data efficiently. Considering their limitations, we can vary our approach to achieve fast and elegant integrations with our Elasticsearch indexes.
As always, the code is available over on GitHub.















