Spell checking, suggestions & similar documents

This section documents the current Solr line (10.0; 9.10.x the maintained 9.x branch), written and verified against the Apache Solr Reference Guide. No specific patch version is pinned. Some capabilities (the Solr Operator on Kubernetes, the package-manager ecosystem, Learning To Rank model training, and expert plugin development) are linked, not documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Several Solr features go beyond returning the documents that literally match q: correcting a misspelled query, completing it as the user types, listing the raw terms a field actually holds, finding documents similar to a given one, and re-scoring a candidate set with something more expensive — up to a trained machine-learning model — than the base query. This page covers the SpellCheck and Suggester search components, the Terms component, MoreLikeThis, Query Re-Ranking, and Learning To Rank. All of them sit downstream of ordinary querying and scoring; see Relevance & scoring for the score they start from and Function queries for the function syntax several of them embed.

SpellCheck component

The SpellCheck search component suggests corrections for a misspelled query, drawing candidates from the terms actually indexed in a field rather than a fixed dictionary. It is added to a request handler in solrconfig.xml alongside one or more named spellchecker implementations, then triggered per request with spellcheck=true. See Spell Checking.

curl "http://localhost:8983/solr/techproducts/select" \
  --data-urlencode 'q=name:aple' \
  --data-urlencode 'spellcheck=true' \
  --data-urlencode 'spellcheck.q=aple' \
  --data-urlencode 'spellcheck.collate=true' \
  --data-urlencode 'spellcheck.count=5' \
  --data-urlencode 'wt=json'

DirectSolrSpellChecker

DirectSolrSpellChecker is the default and recommended implementation: it compares query terms against the main index’s term dictionary directly, with no separate sidecar index to build or keep in sync, so suggestions are always as fresh as the index itself. Its main tuning knobs are maxEdits (the maximum Damerau-Levenshtein edit distance considered, 1 or 2), minPrefix (how many leading characters must match exactly, which both narrows candidates and speeds the lookup), and maxQueryFrequency (a document-frequency ceiling, absolute or a fraction of the index, above which a term is assumed already correct and not corrected against).

WordBreakSolrSpellChecker

WordBreakSolrSpellChecker handles a different class of typo: words run together or split apart, by combining adjacent query terms and/or breaking a single term into two. combineWords and breakWords (both true by default) toggle each direction; maxChanges caps how many word breaks or combinations one suggestion may apply, and breakSuggestionTieBreaker (MAX_FREQ, MIN_FREQ, MAX_FREQ_PROD) picks which candidate wins when several break the same way. It is normally registered alongside DirectSolrSpellChecker under the same request handler so both run together.

Collation

A per-token correction list is often not directly usable — the caller wants one corrected query to re-run, not a menu of independent suggestions. spellcheck.collate=true asks Solr to assemble the individual token corrections back into one query string that is verified to actually return hits before being returned; spellcheck.maxCollationTries bounds how many candidate collations are tried, and spellcheck.collateExtendedResults=true returns, per collation, which original term each correction replaced. spellcheck.alternativeTermCount also asks for corrections on terms that already match something, useful for a "Showing results for X. Search instead for Y?" experience even when the original query was not a hard miss. spellcheck.build=true (re)builds the underlying dictionary; for DirectSolrSpellChecker that build step is cheap since it reads the live index rather than materializing a copy.

Suggester component

The Suggester search component is a separate, purpose-built autocomplete feature: it serves prefix (and, with the right implementation, infix) completions from an in-memory structure built from a dictionary — a field’s values, a query’s results, or a plain word list — rather than scoring a query against the index at request time. Configure one or more named suggesters under a suggest search component in solrconfig.xml, each naming a lookupImpl and a dictionaryImpl, then query it via the /suggest handler. See Suggester.

Lookup implementations

lookupImpl chooses the in-memory data structure and, with it, what kind of match the suggester can serve:

lookupImpl Behavior

FSTLookupFactory

An automaton (finite state transducer) built over the dictionary. Slowest to build and prefix-only, but the smallest memory footprint of the lookups here — a reasonable default when the dictionary is large and rebuilt infrequently.

FuzzyLookupFactory

Extends the (non-FST-named but FST-based) AnalyzingSuggester with typo tolerance: matches within a configurable Levenshtein edit distance of the prefix, not just an exact prefix.

AnalyzingInfixLookupFactory

Matches the query against tokens anywhere in the suggestion, not only its start, by indexing the dictionary into a small internal Lucene index. Supports weighted entries, contextField-based filtering, and highlighting the matched portion.

BlendedInfixLookupFactory

The same infix matching as AnalyzingInfixLookupFactory, but blends each hit’s stored weight with how close the match is to the start of the text, so a prefix-like match still outranks a match buried mid-string.

FreeTextLookupFactory

Predicts the next likely token from an n-gram language model trained on the dictionary text and the tokens already typed, rather than completing the current partial token — closer to predictive text than classic autocomplete.

Building and querying the suggester

A suggester’s dictionary must be built before it can answer anything, either explicitly with suggest.build=true, on a schedule via buildOnStartup / buildOnCommit in its solrconfig.xml definition, or by calling suggest.reload=true to restore a previously built structure from disk without recomputing it.

# Build the dictionary once (or after enough new content to warrant a refresh).
curl "http://localhost:8983/solr/techproducts/suggest" \
  --data-urlencode 'suggest.build=true' \
  --data-urlencode 'suggest.dictionary=mySuggester'

# Query it as the user types.
curl "http://localhost:8983/solr/techproducts/suggest" \
  --data-urlencode 'suggest.dictionary=mySuggester' \
  --data-urlencode 'suggest.q=elec' \
  --data-urlencode 'suggest.count=5' \
  --data-urlencode 'wt=json'

suggest.dictionary may repeat to query several suggesters in one call and merge their results; suggest.cfq supplies a context-filter value for a suggester whose dictionary declared a contextField, scoping suggestions to, say, only one product category. Because the suggester is a separate structure from the main index, it is a much lower-latency path than running the equivalent prefix query through the standard request handler — the tradeoff is a build step to keep it in sync.

An analyzer-based alternative — an edge_ngram token filter feeding a plain text field, matched with an ordinary query at request time — is covered in Text analysis; it needs no separate build step and composes with normal filtering/faceting, at the cost of enlarging the index and the flexibility the dedicated lookups above trade away.

Terms component

The Terms component lists the raw terms actually stored for a field, each with its document frequency, read directly from the index’s term dictionary — no query, scoring, or analysis of an input string involved. It is useful for populating a facet-value picker, building a custom autocomplete without a Suggester, or simply inspecting what a field’s analyzer produced. terms.fl names the field (repeatable); terms.prefix and terms.regex restrict which terms come back; terms.limit (default 10) and terms.mincount / terms.maxcount bound how many and which terms qualify; terms.sort chooses index (lexicographic) or count (by frequency, the default); and terms.list looks up the document frequency of a specific comma-delimited set of terms instead of enumerating. See Terms Component.

curl "http://localhost:8983/solr/techproducts/terms" \
  --data-urlencode 'terms.fl=cat' \
  --data-urlencode 'terms.prefix=elec' \
  --data-urlencode 'terms.sort=count' \
  --data-urlencode 'terms.limit=20' \
  --data-urlencode 'wt=json'

Terms are returned exactly as stored in the index — after analysis, for an analyzed field — so terms.fl against a text_general field yields individual lowercased/stemmed tokens, while terms.fl against a string/keyword field yields whole untouched values; pick the field accordingly (see Field types).

MoreLikeThis

MoreLikeThis (MLT) finds documents similar to a source document (or a block of free text) by extracting the source’s most "interesting" terms — weighted by term and document frequency, the same intuition behind BM25 — and running the result as an OR’d query against the same or related documents. Solr exposes it three ways. See MoreLikeThis.

Across all three, the shared tuning parameters are mlt.fl (required — the fields to draw interesting terms from; fields with termVectors="true" in the schema, see Schema & fields, are far cheaper to use here since the term statistics are already stored), mlt.mintf (minimum term frequency in the source document for a term to be considered, default 2), mlt.mindf (minimum document frequency across the index, default 5), mlt.maxdf / mlt.maxdfpct (a ceiling excluding overly common terms), mlt.minwl / mlt.maxwl (word-length bounds), mlt.maxqt (maximum interesting terms used in the generated query, default 25), and mlt.boost (boost each term by its computed interestingness instead of treating them equally).

MoreLikeThisHandler

The MoreLikeThisHandler is a dedicated request handler (conventionally mounted at /mlt) that takes either a document q selects or raw content posted as a stream, and returns similar documents directly as the main response — the source document’s own match, if any, is not what the caller wants back.

curl "http://localhost:8983/solr/techproducts/mlt" \
  --data-urlencode 'q=id:MA147LL/A' \
  --data-urlencode 'mlt.fl=cat,manu,features' \
  --data-urlencode 'mlt.mindf=1' \
  --data-urlencode 'mlt.mintf=1' \
  --data-urlencode 'wt=json'

MoreLikeThis search component

The MoreLikeThisComponent instead attaches to an ordinary /select request (mlt=true) and adds a moreLikeThis section to the response with, for each hit in the main result list, its own set of similar documents — the right shape for a "customers who viewed this also viewed…​" list rendered alongside a normal search results page, rather than a single lookup.

curl "http://localhost:8983/solr/techproducts/select" \
  --data-urlencode 'q=cat:electronics' \
  --data-urlencode 'mlt=true' \
  --data-urlencode 'mlt.fl=cat,manu,features' \
  --data-urlencode 'mlt.count=3' \
  --data-urlencode 'wt=json'

The mlt query parser

\{!mlt qf=<fields>}<id> embeds the same similarity logic as a query clause — the counterpart to the two request-level forms above, usable anywhere a query fragment can appear (fq, bq, a sub-query) so "similar to this document" composes with ordinary filtering, boosting, and paging instead of living in a dedicated response section.

curl "http://localhost:8983/solr/techproducts/select" \
  --data-urlencode 'q={!mlt qf=cat,manu,features}MA147LL/A' \
  --data-urlencode 'fq=inStock:true' \
  --data-urlencode 'wt=json'

Query Re-Ranking

Query Re-Ranking runs the request’s normal q as usual to get a candidate set, then re-scores only the top N of those candidates with a second, typically more expensive query — rather than running the expensive query against the whole index. It is invoked with rq=\{!rerank} as an additional (not replacement) parameter alongside q. See Query Re-Ranking.

reRankQuery (required) is the query to re-score with — ordinarily a $ parameter reference so its own local params and syntax do not have to be escaped inside rq itself; reRankDocs (default 200) is how many of the top original-query hits get re-scored, with anything beyond that rank kept at its original score and position; reRankWeight (default 2.0) multiplies reRankQuery’s score before it is added to the original score, so a positive `reRankQuery score always increases a document’s combined score even if its magnitude differs wildly from the base query’s.

curl "http://localhost:8983/solr/techproducts/select" \
  --data-urlencode 'q=cat:electronics' \
  --data-urlencode 'rq={!rerank reRankQuery=$rqq reRankDocs=1000 reRankWeight=3}' \
  --data-urlencode 'rqq={!func}product(popularity,recip(ms(NOW,last_modified),3.16e-11,1,1))' \
  --data-urlencode 'wt=json'

reRankQuery here is itself a function query, but any query can fill the role — a DisMax clause with a different qf, a \{!frange}, or, as covered next, a Learning To Rank model. In fact \{!ltr} below is implemented as its own reranking QParserPlugin and can be used directly as rq without wrapping it in \{!rerank}.

Learning To Rank

Learning To Rank (LTR) replaces (or augments) a hand-tuned scoring formula with a model trained offline on labelled relevance judgments — clicks, human ratings, conversions — and applied to re-rank Solr’s candidate results at query time. Solr does not train the model; LTR’s job is representing features consistently between offline training and online scoring, and evaluating a trained model efficiently as a reranker. Enabling it requires loading the solr-ltr contrib jar via <lib> in solrconfig.xml and declaring the ltr query parser and features document transformer there. See Learning To Rank.

Feature stores and features

A feature store is a named, versioned collection of feature definitions — each one a value computed per document per query, such as a stored field’s value, a function query result, or a query’s match score/count against the document (SolrFeature). Features within one store must have unique names; the same name can be reused across different stores for, say, an A/B-tested feature set. Feature definitions are uploaded as JSON to a REST endpoint, independent of the schema or solrconfig.xml.

curl -X PUT "http://localhost:8983/solr/techproducts/schema/feature-store" \
  -H 'Content-Type: application/json' \
  -d '[
    { "store": "myFeatureStore", "name": "popularity",
      "class": "org.apache.solr.ltr.feature.FieldValueFeature",
      "params": { "field": "popularity" } },
    { "store": "myFeatureStore", "name": "priceMatch",
      "class": "org.apache.solr.ltr.feature.SolrFeature",
      "params": { "q": "{!func}recip(price,1,1000,1000)" } },
    { "store": "myFeatureStore", "name": "originalScore",
      "class": "org.apache.solr.ltr.feature.OriginalScoreFeature", "params": {} }
  ]'

A feature’s params can also reference external feature info (efi.*) supplied at query time — the user’s location, session context, or anything not derivable from the document alone — through a $\{efi.name} placeholder, letting the same trained model take request-specific signals into account.

Models

A model is a JSON document naming a feature store plus the trained weights/structure that turn a feature vector into a single re-rank score, uploaded to the model store the same way. Solr ships LinearModel (a weighted sum, for models like logistic regression), MultipleAdditiveTreesModel (gradient-boosted trees, e.g. LambdaMART, expressed as an ensemble of decision trees), and NeuralNetworkModel; a custom model class can be plugged in for anything else.

curl -X PUT "http://localhost:8983/solr/techproducts/schema/model-store" \
  -H 'Content-Type: application/json' \
  -d '{
    "store": "myFeatureStore",
    "name": "myModel",
    "class": "org.apache.solr.ltr.model.LinearModel",
    "features": [
      { "name": "popularity" },
      { "name": "priceMatch" },
      { "name": "originalScore" }
    ],
    "params": {
      "weights": { "popularity": 1.0, "priceMatch": 0.5, "originalScore": 2.0 }
    }
  }'

The \{!ltr} query and the [features] transformer

Once a model is stored, rq=\{!ltr model=<name> reRankDocs=<n>} re-ranks the top reRankDocs matches from q using that model — \{!ltr} behaves like \{!rerank} internally (it re-scores a bounded top-N rather than the whole result set) so the two are not normally combined for the same request. Any efi.* parameters the model’s features reference are passed alongside it.

curl "http://localhost:8983/solr/techproducts/select" \
  --data-urlencode 'q=cat:electronics' \
  --data-urlencode 'rq={!ltr model=myModel reRankDocs=100 efi.userRegion=EU}' \
  --data-urlencode 'fl=id,name,score,[features store=myFeatureStore]' \
  --data-urlencode 'wt=json'

The [features] document transformer (added to fl in square brackets, like any other transformer) returns each returned document’s raw feature values instead of, or alongside, the model’s final score — essential for logging training data for the next offline training run, and for debugging why a model ranked a document where it did. logAll=true logs every feature in the named store rather than only the ones the active model uses; format=dense (default) or sparse controls how zero/absent values are represented.

Feature selection, labelling, and model training all happen outside Solr; only the trained model and its feature definitions come back in. A typical cycle logs features for production traffic with [features], trains a new model offline against click or judgment data, and uploads the result to the model store to replace the active model — Solr itself never re-trains anything.

  • Relevance & scoring — the base score that Query Re-Ranking and LTR start from and can add to or replace.

  • Function queries — the function syntax feature definitions, bf/boost, and reRankQuery all build on.

  • Text analysis — the edge_ngram/analyzer-based alternative to the Suggester component for prefix autocomplete.

  • Schema & fields — termVectors and other field options MoreLikeThis and LTR feature extraction rely on for performance.

  • Query parsers — local params syntax (\{!name …​}), which \{!mlt}, \{!rerank}, and \{!ltr} all use.