# Elasticsearch Term Query on Text Fields: Best Practices and Analyzer Interaction

> Master Elasticsearch term queries on text fields. Learn analyzer interaction and best practices for exact matches versus user searches. Optimize your Elasticsearch terms.

- Repository: [elastic/elasticsearch](https://github.com/elastic/elasticsearch)
- Tags: best-practices
- Published: 2026-02-18

---

**Use `term` queries on `text` fields only when matching exact analyzed tokens; for user searches, prefer `match` queries that apply the analyzer, or use a `keyword` sub-field for exact string matching.**

When working with Elasticsearch, understanding how the `term` query interacts with `text` fields is critical for accurate search results. Unlike full-text queries, a `term` query seeks exact matches in the inverted index without applying the field's analyzer to the query string. This article examines the source code implementation in `elastic/elasticsearch` to explain when and how to use `term` queries on analyzed text fields.

## How Term Queries Work with Text Fields in Elasticsearch

A `term` query operates on the **exact terms** stored in the inverted index. When you index a document with a `text` field, Elasticsearch runs the configured analyzer during indexing, producing tokens that are stored in the index. The `term` query does **not** run the analyzer on your query input—it looks for the exact byte sequence you provide.

This creates a common pitfall: searching for `"The Great Gatsby"` with a `term` query on a standard analyzed `text` field returns no results, because the analyzer lowercased the text and removed punctuation, storing tokens like `"the"`, `"great"`, and `"gatsby"`.

### The Inverted Index vs. Doc-Values Fallback

According to the source code in [`TextFieldMapper.java`](https://github.com/elastic/elasticsearch/blob/main/TextFieldMapper.java), the `termQuery` method first checks whether the field has indexed terms:

```java
@Override
public Query termQuery(Object value, SearchExecutionContext context) {
    if (indexType().hasTerms()) {
        return super.termQuery(value, context);  // Fast inverted index path
    }
    failIfNotIndexedNorDocValuesFallback(context);
    if (usesBinaryDocValues) {
        return new SlowCustomBinaryDocValuesTermQuery(name(),
               indexedValueForSearch(value));
    } else {
        return SortedSetDocValuesField.newSlowExactQuery(name(),
               indexedValueForSearch(value));
    }
}

```

*(see [`server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java`](https://github.com/elastic/elasticsearch/blob/main/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java), lines 558‑571)*

If `indexType().hasTerms()` returns `true`, Elasticsearch uses the fast inverted index lookup via `super.termQuery()`. If the field has no indexed terms (for example, if `index: false` is set but `doc_values: true`), the query falls back to a **slow doc-values scan** using `SortedSetDocValuesField.newSlowExactQuery` or `SlowCustomBinaryDocValuesTermQuery`.

## Source Code Analysis: Term Query Execution Path

The `TermQueryBuilder` class serves as the DSL entry point for `term` queries. As shown in [`TermQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/TermQueryBuilder.java), it extends `BaseTermQueryBuilder` and passes the value directly to the low-level Lucene query without analysis:

```java
public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
    public static final String NAME = "term";
    private boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
    // Value is passed verbatim to the underlying TermQuery
}

```

*(see [`server/src/main/java/org/elasticsearch/index/query/TermQueryBuilder.java`](https://github.com/elastic/elasticsearch/blob/main/server/src/main/java/org/elasticsearch/index/query/TermQueryBuilder.java), lines 30‑38)*

For the newer `match_only_text` field type, implemented in [`MatchOnlyTextFieldMapper.java`](https://github.com/elastic/elasticsearch/blob/main/MatchOnlyTextFieldMapper.java), the behavior is similar: if the field contains terms, it wraps the query in a `ConstantScoreQuery`; otherwise, it falls back to the same doc-values logic as standard `text` fields.

## Best Practices for Elasticsearch Term Queries on Text Fields

1. **Use `match` or `match_phrase` for user-facing search**  
   These queries apply the field's analyzer to the input, ensuring that tokenization matches the indexed terms. This is the correct approach for full-text search.

2. **Create a `keyword` sub-field for exact matches**  
   Map your text field with a multi-field that includes a `keyword` variant:
   ```json
   "title": {
     "type": "text",
     "fields": {
       "keyword": { "type": "keyword" }
     }
   }
   ```

   Run `term` queries against `title.keyword` for exact string matching.

3. **Avoid `term` queries on analyzed `text` fields unless you know the exact token**  
   If you must query the `text` field directly, ensure your query value matches the analyzer output exactly (e.g., lowercased for the `standard` analyzer).

4. **Do not enable `fielddata` on `text` fields to support `term` queries**  
   Loading fielddata into the JVM heap is expensive and can cause out-of-memory errors. Use the `keyword` sub-field pattern instead.

5. **Be aware of doc-values fallback performance**  
   If a `text` field has `index: false` but `doc_values: true`, a `term` query will execute a slow scan. Ensure `doc_values` is enabled (the default for `text` fields in recent versions) if you must query unindexed fields, but prefer indexed fields for performance.

## Practical Examples: Mapping and Query Strategies

### Recommended Mapping Structure

```json
PUT /books
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      }
    }
  }
}

```

### Correct: Term Query on Keyword Sub-field

```json
GET /books/_search
{
  "query": {
    "term": {
      "title.keyword": {
        "value": "The Great Gatsby"
      }
    }
  }
}

```

### Incorrect: Term Query on Analyzed Text Field

This query returns no results because the `standard` analyzer lowercases tokens during indexing:

```json
GET /books/_search
{
  "query": {
    "term": {
      "title": {
        "value": "The Great Gatsby"
      }
    }
  }
}

```

### Corrected: Match Query for Full-Text Search

```json
GET /books/_search
{
  "query": {
    "match": {
      "title": "The Great Gatsby"
    }
  }
}

```

### Term Query with Doc-Values Fallback

When querying a `text` field with `index: false` but `doc_values: true`:

```json
GET /books/_search
{
  "query": {
    "term": {
      "title": {
        "value": "gatsby",
        "case_insensitive": false
      }
    }
  }
}

```

## Summary

- **Term queries perform exact matching** against the inverted index without applying the field's analyzer, making them unsuitable for full-text search on analyzed `text` fields.
- **Use `match` or `match_phrase` queries** for user search input to ensure the query string undergoes the same analysis as the indexed text.
- **Implement `keyword` sub-fields** for exact string matching requirements, querying them with `term` queries instead of the parent `text` field.
- **Avoid fielddata** on `text` fields due to memory overhead; rely on doc-values or keyword fields instead.
- **Understand the doc-values fallback** in [`TextFieldMapper.java`](https://github.com/elastic/elasticsearch/blob/main/TextFieldMapper.java) (lines 558-571) for unindexed fields, recognizing that this path uses `SlowCustomBinaryDocValuesTermQuery` or `SortedSetDocValuesField.newSlowExactQuery` with significant performance penalties.

## Frequently Asked Questions

### Why does my term query return no results on a text field?

A `term` query searches for the exact byte sequence you provide without running the field's analyzer. If your `text` field uses the `standard` analyzer, it lowercases tokens and removes punctuation during indexing. Searching for `"The Great Gatsby"` (mixed case) fails because the index contains `"the"`, `"great"`, and `"gatsby"`. Use a `match` query instead, or query a `keyword` sub-field for exact matches.

### What is the difference between term and match queries in Elasticsearch?

A `term` query performs exact value matching against the inverted index and does not analyze the query string. A `match` query is a full-text search that applies the field's analyzer to the query input, tokenizing and normalizing it to match the indexed terms. Use `term` for exact values (IDs, enums, keywords) and `match` for user-provided search text.

### When should I use the keyword sub-field instead of querying the text field directly?

Always use the `keyword` sub-field when you need exact string matching, sorting, or aggregations on textual data. The `keyword` type stores the original string value unchanged, making it compatible with `term` queries. Querying the parent `text` field with a `term` query only works if you know the exact analyzed token, which is fragile and analyzer-dependent. The `keyword` sub-field provides a reliable, performant path for exact matches.