From the synonym token filter to multi-language stemmer choice
A search that finds "laptop" but returns nothing for "notebook", or that treats "running" and "ran" as completely different words, loses users before the first click. Synonyms and stemming solve exactly this problem when combined correctly in the analyzer, but the details of synonym_graph, stemmer type and language selection decide whether the search becomes more precise or just fuzzier.
Table of Contents
- 1. Why synonyms and stemming decide search relevance
- 2. The synonym token filter: basics and syntax
- 3. synonym_graph vs. synonym: the differences in detail
- 4. Stemmer types compared: Snowball vs. Light Stemmer
- 5. Analyzer chain: combining synonyms and stemming correctly
- 6. Multi-language: stemming pitfalls for German, English & co
- 7. Managing synonyms dynamically with the synonym_set API
- 8. Testing with the Analyze API
- 9. Performance and index size impact
- 10. Summary
- 11. FAQ
1. Why synonyms and stemming decide search relevance
A full-text search that only matches exact word forms fails against the everyday language of its users. Someone typing "cell phone" often means the same thing as someone typing "smartphone". Someone searching for "run" expects results that contain "runs", "ran" or "running" as well. This is exactly where two different but complementary mechanisms come in: synonyms link terms with the same meaning, while stemming reduces word forms to their common root. Both mechanisms live in the analyzer, the chain of tokenizer and token filters that breaks text into comparable tokens during indexing and querying.
The effect of a clean synonym and stemming configuration is measurable: recall improves because more relevant documents are found, without precision necessarily suffering, provided the rules are precise enough. If stemming is applied too aggressively or synonyms are defined too generously, the effect reverses: the search becomes fuzzy, returns irrelevant hits, and users lose trust in result relevance. The following sections show how the synonym token filter, synonym_graph and the right stemmer choice are configured so that synonyms and stemming improve search quality instead of diluting it.
One important principle up front: synonyms and stemming are not a substitute for a good relevance strategy, they are a complement to it. Anyone who looks at both mechanisms in isolation, without considering analyzer order, language and domain vocabulary, tends to produce configurations that look good in tests but deliver surprising hits in production.
2. The synonym token filter: basics and syntax
The synonym token filter replaces or extends tokens in the analyzer based on a rule list. Each rule follows the format term1, term2 => target term for a directed replacement, or term1, term2, term3 for an undirected equivalence group, where every term is replaced or supplemented by all the others. The rules can be written inline in the analyzer definition or loaded from an external file in the cluster's configuration directory, which is the better choice for larger synonym lists.
The distinction between expand: true and an explicit replacement rule matters. In an undirected group like laptop, notebook, netbook with expansion active, each of these words is replaced during indexing by all three variants, so a document containing "laptop" is also found when searching for "notebook". In a directed rule like notebook => laptop, "notebook" is exclusively replaced by "laptop", the reverse direction does not work automatically. This decision directly affects index size and search behavior and should be made deliberately, not adopted as a default.
PUT /products
{
"settings": {
"analysis": {
"filter": {
"product_synonyms": {
"type": "synonym",
"synonyms": [
"laptop, notebook, netbook",
"cell phone, smartphone, mobile phone",
"headphones => headphones, headset"
]
}
},
"analyzer": {
"product_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "product_synonyms"]
}
}
}
},
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "product_analyzer" }
}
}
}
The synonym token filter can be applied both at index time and at query time. Common practice is to expand synonyms only during indexing, so the index contains all variants while the search query stays unchanged. That reduces the compute cost per query, because expansion does not happen again for every single search, but only once at index time.
3. synonym_graph vs. synonym: the differences in detail
The plain synonym filter reaches its limits as soon as multi-word synonyms come into play, for instance "new york" as a synonym for "big apple". Because the standard token filter processes tokens individually, such cases produce an incorrect positioning in the token graph, leading to wrong or missing hits on phrase queries. This is exactly what the synonym_graph filter was built for: it produces a correct token graph with position increments that also represents multi-word synonyms correctly.
The rule of thumb in practice: synonym_graph should be used in all analyzers that run at index time and might contain multi-word synonyms, while the plain synonym filter is sufficient for search-time analyzers, as long as no graph queries such as match_phrase with synonym expansion are involved. A common mistake is applying synonym_graph everywhere by default: the filter is more compute-intensive, and for plain single-word synonyms it offers no benefit over the plain synonym filter.
PUT /products/_settings
{
"index": {
"analysis": {
"filter": {
"multiword_synonyms": {
"type": "synonym_graph",
"synonyms": [
"new york, big apple",
"usb type c, usb-c, usb type-c"
]
}
},
"analyzer": {
"index_time_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "multiword_synonyms"]
}
}
}
}
}
Another difference concerns the combination with stemming. If a synonym_graph filter is placed before a stemmer, unexpected results can occur because the stemmer processes the already expanded synonyms again and produces stems that no longer match the original intent. The recommended order for this is explained in detail in the analyzer chain section.
4. Stemmer types compared: Snowball vs. Light Stemmer
Stemming reduces word forms to a common root so that "run", "runs" and "running" produce the same token during indexing. Elasticsearch offers several algorithms for this, with the two most important families being the Snowball Stemmer and the Light Stemmer. The Snowball Stemmer, based on the Porter algorithm and its language-specific derivations, reduces words aggressively to a minimal stem. This noticeably increases recall, but can also incorrectly merge different words with a similar stem, an effect known in the literature as overstemming.
The Light Stemmer follows a more conservative approach: it only removes the most common inflectional endings, such as plural s or simple verb endings, without deeply intervening in word morphology. The result is less aggressive stemming with a lower risk of overstemming, but also lower recall for complex inflectional forms. For German texts with their rich morphology this means: a Snowball Stemmer for German frequently merges "Wanderung" (hike), "Wanderer" (hiker) and "wandern" (to hike) under the same stem, which can be desirable for broad queries but leads to unwanted hits for precise technical terms.
PUT /articles
{
"settings": {
"analysis": {
"filter": {
"german_light_stemmer": {
"type": "stemmer",
"language": "light_german"
},
"german_aggressive_stemmer": {
"type": "stemmer",
"language": "german2"
}
},
"analyzer": {
"de_light": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "german_normalization", "german_light_stemmer"]
},
"de_aggressive": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "german_normalization", "german_aggressive_stemmer"]
}
}
}
}
}
In Elasticsearch the lighter algorithm for German is called light_german, while german2 represents the more aggressive Snowball variant with an extended rule base. For e-commerce searches with many product names and technical terms, light_german is generally recommended, because product names are rarely inflected heavily and an aggressive stemmer tends to create noise rather than benefit here. For full-text search across editorial content with natural running text, the more aggressive stemmer often delivers the better recall.
5. Analyzer chain: combining synonyms and stemming correctly
The order of filters in the analyzer chain is not a formality, it directly affects the outcome. The established best practice is: first normalization such as lowercase and german_normalization, then the synonym filter, and only after that the stemmer. The reason: synonyms are usually defined in their base form, and if the stemmer runs before the synonym filter, the already stemmed word form often no longer matches the synonym rule, causing synonym recognition to silently fail.
A second, often overlooked point: if stemming is applied before the synonym_graph filter, the synonym list must also be maintained in stemmed form, which makes maintaining the list significantly harder and more error-prone. The recommended chain lowercase → synonym_graph → stemmer avoids this problem, because synonyms can be maintained in their natural spelling and the stemmer only afterward normalizes both original words and expanded synonyms equally.
PUT /knowledge_base
{
"settings": {
"analysis": {
"filter": {
"domain_synonyms": {
"type": "synonym_graph",
"synonyms": ["laptop, notebook", "server, data center server"]
},
"de_stemmer": {
"type": "stemmer",
"language": "light_german"
}
},
"analyzer": {
"combined_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"german_normalization",
"domain_synonyms",
"de_stemmer"
]
}
}
}
},
"mappings": {
"properties": {
"content": { "type": "text", "analyzer": "combined_analyzer" }
}
}
}
A common mistake in practice is using the same analyzer chain for both indexing and searching without checking whether that is actually the desired behavior. If the synonym_graph filter is also applied at search time, every query expands again, which can noticeably increase query latency for large synonym lists. In many cases it is more performant to expand synonyms only at index time and define a separate search_analyzer without a synonym filter for the search query.
6. Multi-language: stemming pitfalls for German, English & co
Multi-language indices are one of the most common sources of error in stemming configurations. If a single analyzer with a German stemmer is applied to an index that also contains English or French content, the stemmer produces wrong or meaningless stems for foreign-language words, because the rule base is language-specific. The result is a search that works well for one language and performs noticeably worse for every other language in the same index.
The robust solution is to create a separate field with its own analyzer per language, for example title_de with a German stemmer and title_en with an English stemmer, and to direct the search query to the matching field depending on the detected or configured user language. Alternatively, separate indices per language can be used, which also brings advantages for index management and sharding when content volume differs strongly between languages. Important here: synonym lists are language-specific too, a German synonym pair like "Rechner, Computer" has no direct equivalent in English and must be defined separately.
PUT /multilingual
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"de": { "type": "text", "analyzer": "de_light" },
"en": { "type": "text", "analyzer": "english" },
"fr": { "type": "text", "analyzer": "french" }
}
}
}
}
}
GET /multilingual/_search
{
"query": {
"match": {
"title.en": "running shoes for marathon"
}
}
}
Another pitfall concerns compound words in German. "Laufschuh" is a single token, while the English equivalent "running shoe" consists of two tokens. A stemmer alone does not solve this problem, here a decompounder such as hyphenation_decompounder helps, which splits compound German words into their components before stemming and synonym recognition apply. Without a decompounder, a search for "Schuh" (shoe) finds no document that only contains "Laufschuh", even though that would be a reasonably close match.
7. Managing synonyms dynamically with the synonym_set API
Traditionally, synonym lists were maintained in files inside the config directory of every cluster node, which required manually copying the file to every node and reloading the analyzer. Since Elasticsearch 8.10, the synonym_set API structurally solves this problem: synonyms are managed centrally through the API and automatically distributed to all nodes, without manual file handling.
Analyzers referencing a synonym_set also support a reload without a full index reindex: after updating the synonym list, a single POST /index/_reload_search_analyzers is enough for new search queries to use the updated rules, while already indexed documents remain unchanged. For use cases where synonyms change continuously, for instance driven by click-through feedback, this is a substantial operational advantage over file-based management.
PUT /_synonyms/product_synonyms
{
"synonyms_set": [
{ "id": "syn-1", "synonyms": "laptop, notebook, netbook" },
{ "id": "syn-2", "synonyms": "headphones => headphones, headset" }
]
}
PUT /products/_settings
{
"index": {
"analysis": {
"filter": {
"product_synonyms": {
"type": "synonym_graph",
"synonyms_set": "product_synonyms",
"updateable": true
}
}
}
}
}
POST /products/_reload_search_analyzers
8. Testing with the Analyze API
Without systematic testing, every synonym and stemming configuration remains a guess. The _analyze API allows sending arbitrary text through a defined analyzer and seeing exactly which tokens are produced. It is the fastest way to check whether a synonym rule actually applies and whether the chosen stemmer produces the expected word stems, before the configuration goes to production.
POST /products/_analyze
{
"analyzer": "product_analyzer",
"text": "I am looking for a good notebook with headphones"
}
# Response shows every token with position and type
# {
# "tokens": [
# { "token": "i", "start_offset": 0, "end_offset": 1, ... },
# { "token": "am", "start_offset": 2, "end_offset": 4, ... },
# { "token": "good", ... },
# { "token": "laptop", "start_offset": 24, "end_offset": 32, ... },
# { "token": "notebook", "start_offset": 24, "end_offset": 32, ... },
# { "token": "netbook", "start_offset": 24, "end_offset": 32, ... },
# { "token": "headphones", ... },
# { "token": "headset", ... }
# ]
# }
A proven approach is to build a small test suite with representative example sentences for every new synonym rule and every stemmer change, and to run the Analyze API against this suite automatically. This makes regressions visible before they lead to wrong search results in production. Especially for stemming, it pays off to specifically test word pairs that could be incorrectly merged, for example technical terms with a similar stem but different meaning.
9. Performance and index size impact
Synonyms and stemming are not free. Every synonym rule with expand behavior increases the number of tokens per document, which directly grows the size of the inverted index. For large synonym lists with hundreds of entries, index size can grow noticeably, especially when many terms are grouped into large equivalence groups. Stemming, on the other hand, tends to reduce the number of unique terms in the index, because multiple word forms map to the same stem, which has a positive effect on index size.
Regarding query performance: if the synonym_graph filter is applied at search time, every query pays extra compute time for expansion. For high-traffic search endpoints it is therefore advisable to expand synonyms only at index time and keep the search-time analyzer lean. A benchmark with realistic search queries before going live reliably shows whether a chosen combination of synonyms and stemming still meets latency requirements, particularly in autocomplete scenarios with high query frequency.
| Criterion | Snowball Stemmer (e.g. german2) | Light Stemmer (e.g. light_german) |
|---|---|---|
| Aggressiveness | High, reduces to a minimal stem | Low, removes only common endings |
| Recall | High, more word forms merged | Moderate, more conservative merging |
| Overstemming risk | Elevated, different words merge | Low, more precise word stem |
| Recommended use | Editorial full-text search, running text | E-commerce, product names, technical terms |
| Index size | Smaller, fewer unique terms | Slightly larger than Snowball |
The table shows: there is no universally correct stemmer, only the right choice for the given use case. If unsure, start with the Light Stemmer and observe recall based on real search queries and click data, before switching to a more aggressive Snowball Stemmer.
Mironsoft
Elasticsearch architecture, search quality and relevance tuning
A search that finds what users actually mean?
We analyze your analyzer configuration, build fitting synonym lists and choose the right stemmer strategy for your language and domain, with a measurable effect on recall and precision.
Analyzer Audit
Review existing configuration for overstemming and missing synonyms
Synonym Setup
Make domain-specific synonym lists maintainable with the synonym_set API
Multi-language
Set up language-specific fields and stemmers for international shops
10. Summary
Synonyms and stemming solve different problems that together shape the search quality of an Elasticsearch search decisively. The synonym token filter links terms with the same meaning, with synonym_graph being indispensable for multi-word synonyms. Stemming reduces word forms to a common root, where the choice between an aggressive Snowball Stemmer and a more conservative Light Stemmer directly affects recall and overstemming risk. The order in the analyzer chain, normalization first, then synonyms, then stemming, is critical for both mechanisms to work together correctly.
Multi-language indices need their own field per language with a matching stemmer and matching synonym list, since a single analyzer never works equally well for all languages. The synonym_set API significantly simplifies the operational maintenance of synonyms compared to file-based lists. Systematic testing with the Analyze API and an eye on performance impact round off a solid synonyms and stemming configuration before it goes to production.
Configuring synonyms and stemming, the essentials at a glance
Synonym Filter
synonym_graph for multi-word synonyms, synonym for simple single-word cases. Choose expand deliberately.
Stemmer Choice
Light Stemmer for product names and technical terms, Snowball for editorial full-text search.
Filter Order
lowercase, then synonym_graph, then stemmer. Prevents synonym recognition from silently failing.
Multi-language
One field and analyzer per language instead of a universal configuration for all languages.