Elasticsearch Match Query vs Term Query: When to Use Each for Full-Text vs Exact Match

Use a match query for full-text search on analyzed text fields where you need tokenization, stemming, and relevance scoring; use a term query for exact-value filtering on keyword, numeric, or other non-analyzed fields where the value must match verbatim without analysis.

The match and term queries in Elasticsearch serve fundamentally different search purposes despite both locating documents containing specific values. According to the elastic/elasticsearch source code, the divergence begins at the analysis layer: MatchQueryBuilder processes input through the field's analyzer while TermQueryBuilder compares values directly against the indexed term dictionary without transformation.

Core Architectural Differences

The primary distinction between these query types lies in whether the input text undergoes analysis before reaching Lucene's index.

How Match Query Processes Text

When you execute a match query, the MatchQueryBuilder class in server/src/main/java/org/elasticsearch/index/query/MatchQueryBuilder.java orchestrates a multi-stage analysis pipeline. The builder's doToQuery method instantiates a MatchQueryParser (located in server/src/main/java/org/elasticsearch/index/search/MatchQueryParser.java) which applies the field's configured analyzer—tokenizing the input, converting to lowercase, applying stemming, and removing stop words.

The parser then constructs a Lucene Boolean query from the resulting tokens. By default, these tokens are joined with a Boolean OR clause, though you can specify operator: and to require all tokens. This architecture supports advanced features like fuzziness, prefix_length, and minimum_should_match parameters, which are forwarded directly to the MatchQueryParser.

How Term Query Handles Values

In contrast, the TermQueryBuilder in server/src/main/java/org/elasticsearch/index/query/TermQueryBuilder.java bypasses analysis entirely. Its doToQuery method retrieves the MappedFieldType for the target field and invokes mapper.termQuery(value, context), creating a Lucene TermQuery that compares the supplied value verbatim against the indexed term dictionary.

Because no analyzer intervenes, the term query performs exact byte-level matching. This makes it ideal for keyword fields, numeric identifiers, and other non-analyzed data. The builder also implements maybeRewriteBasedOnConstantFields, which optimizes execution by rewriting constant fields to match_all or match_none without disk I/O.

Practical Usage Patterns

Understanding the implementation details guides proper field selection and query construction.

Full-Text Search with Match Query

Use the match query when searching analyzed text fields like article titles, product descriptions, or log messages. The query automatically handles tokenization and normalization, allowing "Quick Brown Fox" to match documents containing "quick brown foxes" due to stemming and lowercasing.

GET products/_search
{
  "query": {
    "match": {
      "description": {
        "query": "quick brown fox",
        "operator": "and",
        "fuzziness": "AUTO",
        "minimum_should_match": "75%"
      }
    }
  }
}

In this example, MatchQueryBuilder analyzes the input into individual tokens and constructs a Boolean query requiring all terms (operator: and) while allowing for minor misspellings (fuzziness: AUTO).

Exact Value Filtering with Term Query

Apply the term query for precise filtering on keyword fields, status codes, user IDs, or enumerated values. Since TermQueryBuilder performs no analysis, the value must match the indexed term exactly, including case sensitivity (unless using the case_insensitive flag available since version 7.10).

GET users/_search
{
  "query": {
    "term": {
      "status": {
        "value": "ACTIVE",
        "case_insensitive": true
      }
    }
  }
}

This query executes a verbatim comparison against the status keyword field. If the field were analyzed text instead of keyword, this query would likely return no results because the indexed terms would be individual words, not the full string "ACTIVE".

Key Behavioral Differences

Several operational characteristics distinguish these queries beyond simple analysis.

Multi-Term Handling

The match query naturally handles multi-word input by analyzing it into separate tokens and constructing a Boolean query. The term query accepts only a single value; to match multiple exact values, you must use a terms query or combine multiple term clauses within a bool query.

GET orders/_search
{
  "query": {
    "bool": {
      "should": [
        { "term": { "order_status": "PENDING" } },
        { "term": { "order_status": "SHIPPED" } }
      ],
      "minimum_should_match": 1
    }
  }
}

Query Rewrites and Optimizations

Both builders implement rewrite logic to optimize execution. MatchQueryBuilder.doIndexMetadataRewrite detects when a field uses a KeywordAnalyzer and rewrites to a TermQueryBuilder for efficiency. Conversely, TermQueryBuilder.maybeRewriteBasedOnConstantFields rewrites queries against constant fields to match_all or match_none, eliminating unnecessary disk I/O.

Fuzziness and Prefix Support

Only the match query supports fuzzy matching, prefix queries, and wildcard expansions through the MatchQueryParser. The term query provides no fuzzy capabilities; exact byte-matching is the only mode of operation, though the case_insensitive parameter allows for case-normalized exact matching on keyword fields.

Summary

  • Use match queries for full-text search on analyzed fields like text types, where you need tokenization, stemming, relevance scoring, and support for fuzzy matching.
  • Use term queries for exact-value filtering on keyword, numeric, or other non-analyzed fields, where the value must match verbatim without analysis.
  • Remember the analysis layer: MatchQueryBuilder processes input through the field's analyzer via MatchQueryParser, while TermQueryBuilder in server/src/main/java/org/elasticsearch/index/query/TermQueryBuilder.java creates a Lucene TermQuery with no analysis.
  • Handle multiple values correctly: Use bool queries with multiple term clauses or a terms query for exact matching against multiple values, since term queries only accept single values.

Frequently Asked Questions

Can I use a match query for exact matches?

You can, but only if the field uses a keyword analyzer or is a keyword field type. In MatchQueryBuilder.doIndexMetadataRewrite, the builder automatically rewrites to a TermQueryBuilder when it detects a KeywordAnalyzer, effectively performing an exact match. However, for text fields, a match query will analyze the input and potentially match multiple documents due to tokenization, making it unsuitable for precise exact matching.

Why does my term query return no results on text fields?

A term query performs no analysis on the input value, comparing it verbatim against the indexed terms. When you apply a term query to an analyzed text field, the indexed terms are typically individual words (tokens) that have been lowercased and stemmed. If you search for "Quick Brown Fox" using a term query, it looks for that exact string including capitalization and spaces, which won't match the indexed tokens "quick", "brown", and "fox". Use a match query for text fields instead.

When should I use a terms query instead of term query?

Use a terms query when you need to match a single field against multiple exact values and want cleaner syntax than a bool query with multiple term clauses. While combining multiple term queries in a bool query's should clause works for OR logic, a terms query is more concise for matching any of a list of values. Additionally, terms queries can be more efficient for large lists of values as they reduce query parsing overhead and leverage specific optimizations in the Lucene TermInSetQuery.

Does case_insensitive work on all field types?

No, the case_insensitive parameter only works on keyword fields and certain other string-based field types that support normalization. As implemented in TermQueryBuilder.doToQuery, the parameter triggers a case-insensitive term query only when the field mapper supports it. For text fields or numeric fields, the case_insensitive flag has no effect because text fields are analyzed (where case handling happens at index time via analyzers) and numeric fields have no case concept. Always ensure your mapping uses the keyword field type if you need case-insensitive exact matching with the term query.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →