All findings · Lucene finding · LFV-1072 · wordbreak-combinations-applies-minsuggestionfrequency-under-more-popular

WordBreakSpellChecker.suggestWordCombinations applies minSuggestionFrequency under SUGGEST_MORE_POPULAR, although its setter says the minimum does not apply there

With setMinSuggestionFrequency(2) and SUGGEST_MORE_POPULAR, suggestWordCombinations([sun, flower]) drops the indexed word sunflower (docFreq 1), although the setter says the minimum is "Not applicable" in that mode and suggestWordBreaks replaces it there. One setting means two things in a checker; a one-line change makes the two methods agree.

Summary

Priority
Low, rank 454In every release since 4.0, WordBreakSpellChecker word combinations still apply a non-default minimum suggestion frequency under SUGGEST_MORE_POPULAR, although the setter says it does not apply there, and silently drop valid combined words. It needs that setting pair, which downstream spell-check settings can produce, and mostly affects rare words, so it is Low.
Impact
minor
Since
4.0.0-ALPHA (2012), the class's first commitintroduced 2012-05-30 · over 14 years
Affected
4.0.0-ALPHA to 10.6.0 and main; 17 versions run
Status
documented
Found by
bounded-exhaustive comparison with reference models

AI-assisted page: this finding was first signalled by a semi-formal check (an exact check of the real jar, or a modelling hypothesis that a tool then witnessed) and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

WordBreakSpellChecker suggests joining words typed apart (sun flower -> sunflower) and splitting a word into indexed parts. Its one minSuggestionFrequency setting is documented as not applying in more-popular mode. The split method replaces it there; the join method still checks it after its own more-popular rule.

  1. Index one document with the single word sunflower (docFreq 1); sun and flower are not in the index. Call setMinSuggestionFrequency(2), whose javadoc says the minimum is "Not applicable when used with SUGGEST_MORE_POPULAR".
  2. Call suggestWordCombinations([sun, flower], 5, reader, SUGGEST_MORE_POPULAR). You expect sunflower: it is in the index and at least as frequent (1) as its most popular part (0), the mode's documented rule.
  3. You get no suggestion and no error: after the more-popular rule passes, a second check still requires docFreq >= 2. With the default minimum of 1 the same call returns sunflower.
  4. The sibling method keeps the setter's promise: with sun and flower indexed once each, suggestWordBreaks("sunflower") with the same minimum and mode returns [sun, flower], although each part has docFreq 1 < 2.

Who is affected

Users who set a minimum suggestion frequency of 2 or more and ask suggestWordCombinations for more-popular suggestions, directly or through a word-break spell-check component that maps such options onto these calls. The default minimum and the other modes are unaffected. Effect: a valid join suggestion is missing; nothing else.

Mechanism, terms and numbers493 words

WordBreakSpellChecker (lucene-suggest) suggests joining terms a user typed apart (suggestWordCombinations, sun flower -> sunflower) and splitting one term into indexed words (suggestWordBreaks). Its setMinSuggestionFrequency javadoc says the minimum is "Not applicable when used with SUGGEST_MORE_POPULAR". suggestWordBreaks follows that: in that mode it replaces the minimum with the term's own docFreq (at least 1). suggestWordCombinations does not: after its more-popular rule it still requires combinedTermFreq >= minSuggestionFrequency, in every mode. With setMinSuggestionFrequency(2), an index holding only sunflower (docFreq 1) and SUGGEST_MORE_POPULAR, suggestWordCombinations([sun, flower]) returns nothing instead of sunflower, while on an index with sun and flower suggestWordBreaks("sunflower") returns [sun, flower] under the same settings. The default minimum (1) and the other modes are unaffected, nothing throws, and the only effect is a missing suggestion. It dates from the class's first commit (LUCENE-3523, 2012) and reproduces on every version run, 4.0.0-ALPHA to 10.6.0 and main. A one-line change that mirrors suggestWordBreaks fixes it; maintainers may prefer to narrow the javadoc instead.

Terms

  • WordBreakSpellChecker (lucene-suggest, org.apache.lucene.search.spell): suggestWordCombinations(terms, ...) joins adjacent query terms into one indexed word; suggestWordBreaks(term, ...) splits one term into indexed words.
  • SuggestMode.SUGGEST_MORE_POPULAR: "Return only suggested words that are as frequent or more frequent than the searched word" (SuggestMode.java:28). For joins the method javadoc says "each suggestion will have the same, or better frequency than the most-popular included term" (WordBreakSpellChecker.java:142-143).
  • minSuggestionFrequency (default 1, one field read by both methods): "The minimum frequency a term must have to be included as part of a suggestion. Default=1 Not applicable when used with SUGGEST_MORE_POPULAR" (:413-416).

The bug

  1. suggestWordBreaks keeps the promise: under SUGGEST_MORE_POPULAR, useMinSuggestionFrequency = (origFreq == 0 ? 1 : origFreq) (:104-107), so the configured minimum is not used.
  2. suggestWordCombinations checks the more-popular rule combinedTermFreq >= maxFreq (:206), where maxFreq is the docFreq of the most popular included term, and then combinedTermFreq >= minSuggestionFrequency in every mode (:208).
  3. So in more-popular mode a join that passes the method's other checks (separators, maxChanges, word length, evaluation and result limits) is dropped exactly when maxFreq <= combinedTermFreq < minSuggestionFrequency: the joined word and all its parts are rarer than the minimum. The default minimum (1) and the other two modes behave the same with and without the fix.

The numbers

One document, field f, stock main, assertions on (the last column: the same run with the fix):

indexcallminimumALWAYSWHEN_NOT_IN_INDEXMORE_POPULARMORE_POPULAR, fixed
sunflowersuggestWordCombinations([sun, flower])1sunflowersunflowersunflowersunflower
sunflowersuggestWordCombinations([sun, flower])2nonenonenonesunflower
sun, flowersuggestWordBreaks("sunflower")1[sun, flower][sun, flower][sun, flower][sun, flower]
sun, flowersuggestWordBreaks("sunflower")2nonenone[sun, flower][sun, flower]

Row 4 is the setter's promise kept by word breaks (each part has docFreq 1 < 2, yet the split is offered); row 2 is the same promise broken by word joins. The fix changes exactly one cell.

Exposure and likelihood163 words
  • Who. Users of WordBreakSpellChecker who call setMinSuggestionFrequency with 2 or more and ask suggestWordCombinations for SUGGEST_MORE_POPULAR suggestions, directly or through a downstream word-break spell-check component.
  • Not affected. The default minimum (1), and the modes SUGGEST_ALWAYS and SUGGEST_WHEN_NOT_IN_INDEX.
  • Effect. A valid join suggestion is silently missing. No exception, no index change, no change to search hits.
  • Scale. Deterministic, with assertions on and off: the condition depends only on the docFreqs of the parts and the joined word, not on index size (the method's other limits apply as usual). The window is narrow: the joined word and all its parts must be rarer than the configured minimum, with the joined word at least as frequent as its most popular part, so it concerns words that are rare relative to the floor the caller chose.
  • How likely. Rare: it needs a non-default minimum and the more-popular mode; the recorded searches found no public report since 4.0 (2012). Plain setter calls suffice, so it is not contrived.

Minimal repro

One JUnit test on the public API: one document with sunflower, a minimum of 2, one suggestWordCombinations call in more-popular mode. Put it in the suggest module's test sources and run one gradle command, with assertions on or off.

TestWordBreakCombineMorePopularMinFreq.java (JUnit, public API)49 lines
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.search.spell;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestWordBreakCombineMorePopularMinFreq extends LuceneTestCase {

  public void testMinSuggestionFrequencyNotAppliedWithMorePopular() throws Exception {
    try (Directory dir = newDirectory()) {
      try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
        Document doc = new Document();
        doc.add(new StringField("f", "sunflower", Field.Store.NO));
        w.addDocument(doc);
      }
      try (IndexReader ir = DirectoryReader.open(dir)) {
        WordBreakSpellChecker wbsp = new WordBreakSpellChecker();
        wbsp.setMinSuggestionFrequency(2);
        Term[] terms = {new Term("f", "sun"), new Term("f", "flower")};
        // expected: [sunflower] (the minimum is "not applicable" with SUGGEST_MORE_POPULAR)
        assertEquals(
            1, wbsp.suggestWordCombinations(terms, 5, ir, SuggestMode.SUGGEST_MORE_POPULAR).length);
      }
    }
  }
}
Command
cp TestWordBreakCombineMorePopularMinFreq.java $LUCENE/lucene/suggest/src/test/org/apache/lucene/search/spell/
cd $LUCENE
./gradlew :lucene:suggest:test --tests org.apache.lucene.search.spell.TestWordBreakCombineMorePopularMinFreq -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off (production-like): use -Ptests.asserts=false instead (same failure)
Expected
One suggestion, sunflower: docFreq 1, at least as frequent as its parts sun and flower (0 each).
Observed
No suggestion: expected:<1> but was:<0>, with assertions on and off; no error.

All files of this repro: repro/LFV-1072/

More files, environment, full output167 words

The test asserts the number of suggestions (the comment names the expected word, sunflower). It is the hunt's minimal repro with one comment line shortened so the module's format check passes; the code is unchanged. It uses public API only; this exact file compiles and fails unchanged on 9.1.0, 9.12.3, 10.6.0 and main (run); on 9.0.0 and older LuceneTestCase is in org.apache.lucene.util. The same test is the suggested regression test.

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT); the same test also fails on the 10.6.0 and 9.12.3 release jars
jdkOpenJDK 25.0.3
osLinux aarch64

Observed output

# gradle, main 645d4fe14a20, seed DEADBEEF, -Ptests.asserts=true (the same with -Ptests.asserts=false)
TestWordBreakCombineMorePopularMinFreq > testMinSuggestionFrequencyNotAppliedWithMorePopular FAILED
    java.lang.AssertionError: expected:<1> but was:<0>
        at org.apache.lucene.search.spell.TestWordBreakCombineMorePopularMinFreq.testMinSuggestionFrequencyNotAppliedWithMorePopular(TestWordBreakCombineMorePopularMinFreq.java:44)
TestWordBreakSpellChecker (upstream): tests=5 failures=0

# release jars 10.6.0, 9.12.3 and 9.1.0, standalone runner, this exact file, assertions on: 1 of 1 fails, same message

# with the fix: the whole :lucene:suggest:test plus the repro, 32 classes, 237 tests, 0 failures (7 skipped by the module), assertions on and off

Why it looks unintended

  • The setter's javadoc says the minimum is "Not applicable when used with SUGGEST_MORE_POPULAR", with no method scope; both methods read the one field.
  • The sibling suggestWordBreaks implements that sentence with dedicated code from the same commit.
  • The join method's own rule ("the same, or better frequency than the most-popular included term") has no minimum; no reviewed test endorses the difference.
The full argument190 words
  • Contract. setMinSuggestionFrequency: "The minimum frequency a term must have to be included as part of a suggestion. Default=1 Not applicable when used with SUGGEST_MORE_POPULAR" (WordBreakSpellChecker.java:413-416). Nothing limits the sentence to one method.
  • Sibling method. suggestWordBreaks replaces the minimum with origFreq == 0 ? 1 : origFreq in that mode (:104-107), written in the same commit by the same author (642b882d5a5). That code can even go below the configured minimum, so "not applicable" was meant literally.
  • The method's own rule. "When suggestMode equals SUGGEST_MORE_POPULAR, each suggestion will have the same, or better frequency than the most-popular included term" (:142-143); SuggestMode says "as frequent or more frequent than the searched word" (SuggestMode.java:28). Neither mentions a minimum.
  • Tests and history. Upstream TestWordBreakSpellChecker never uses SUGGEST_MORE_POPULAR (its suggestWordCombinations calls use SUGGEST_ALWAYS and SUGGEST_WHEN_NOT_IN_INDEX); no CHANGES, MIGRATE or issue entry found in the recorded searches discusses the rule. In SOLR-2993, the Solr integration of this class, its author describes the only-more-popular join rule as "a document frequency that is greater or equal than the highest document frequency between "spell" and "check"", with no minimum.
The devil's-advocate check180 words

The strongest works-as-designed case (the cross-model lenses rated it moderate; the credibility audit leans on it): minSuggestionFrequency is a global quality floor and more-popular an extra relative rule. DirectSpellChecker in the same package keeps its thresholdFrequency floor under SUGGEST_MORE_POPULAR (DirectSpellChecker.java:350-356). The join javadoc gives a necessary condition, not a promise to return every qualifying word. The behaviour is 14 years old with no public complaint found, SuggestMode is experimental, and Solr's reference guide presents the minimum as a plain floor, so a user who sets 2 may expect it to apply.

Why it fails as a defence of the current code: DirectSpellChecker's threshold javadoc makes no "not applicable" promise, while this class's setter does, in so many words; the sibling method of the same checker implements the promise; and nothing found endorses applying the minimum to joins only. What the case does show is that the fix direction (code, javadoc, or the minimum in both methods) is the maintainers' choice, which is why the bug ranks Low. Verdict: not intended; one setting, two meanings in a checker.

Suggested fix

Recommended A: use 1 instead of the minimum under SUGGEST_MORE_POPULAR (mirrors suggestWordBreaks)

Under SUGGEST_MORE_POPULAR, compare the joined word's docFreq with 1 instead of minSuggestionFrequency, as suggestWordBreaks does (+7/-1 lines). With it the whole suggest module passes, assertions on and off. Maintainers may instead narrow the setter's javadoc or apply the minimum in both methods.

// minSuggestionFrequency is "not applicable" with SUGGEST_MORE_POPULAR (see its setter), and
// suggestWordBreaks replaces it in that mode. Here, in that mode, a combined term only has to
// exist and be at least as frequent as the most popular included term
int useMinSuggestionFrequency =
    suggestMode == SuggestMode.SUGGEST_MORE_POPULAR ? 1 : minSuggestionFrequency;
...
            if (combinedTermFreq >= useMinSuggestionFrequency) {
All options and trade-offs3 options ยท 536 words

Option A was built and run in a scratch worktree at 645d4fe14a20; B and C are listed as alternatives. A is recommended because it makes the code match its documentation and the two methods agree, with no change for the default minimum.

A: use 1 instead of the minimum under SUGGEST_MORE_POPULAR (mirrors suggestWordBreaks) (validated by the checks) (recommended)

In suggestWordCombinations, compute the minimum once before the loop and use it in the check at WordBreakSpellChecker.java:208. A joined word then has to exist and be at least as frequent as its most popular part, the rule suggestWordBreaks applies.

// minSuggestionFrequency is "not applicable" with SUGGEST_MORE_POPULAR (see its setter), and
// suggestWordBreaks replaces it in that mode. Here, in that mode, a combined term only has to
// exist and be at least as frequent as the most popular included term
int useMinSuggestionFrequency =
    suggestMode == SuggestMode.SUGGEST_MORE_POPULAR ? 1 : minSuggestionFrequency;
...
            if (combinedTermFreq >= useMinSuggestionFrequency) {

Trade-offs

  • No change for the default minimum (1) or for the other two modes.
  • Callers who relied on a minimum of 2 or more for joins in more-popular mode get more suggestions: a behaviour change for them.
  • For a minimum of 0 or below, A also stops suggesting a joined word that is not in the index (docFreq 0) in more-popular mode, as suggestWordBreaks already does (its replacement never goes below 1). Today 0 >= 0 lets such a word through; that face is not claimed as a bug.
  • Open PR #16847 changes docFreq variables in this file (including combinedTermFreq) from int to long; A applies cleanly on top of it and still compiles (the int floor is widened when compared with the long docFreq); a reviewer may prefer long for the new local.

What the checks validated

Gradle in a scratch worktree at 645d4fe14a20, seed DEADBEEF, OpenJDK 25.0.3: on stock the repro fails 1 of 1 and upstream TestWordBreakSpellChecker passes 5 of 5, assertions on and off; with A the whole :lucene:suggest:test plus the repro, 32 classes, 237 tests, 0 failures (7 skipped by the module), assertions on and off; :lucene:suggest:check -x test with A and the repro added passes (google-java-format, ecj lint, forbidden APIs). After the fact check the patch's three comment lines were reworded (code lines unchanged) and the patch re-run: check, and the whole module with assertions on and off, same results. In the hunt's bounded-exhaustive comparison A changes exactly the 99,791 calls that differ from the setter-following reference (73,819 claimed drops at a minimum of 2 or 3, 25,972 minimum-0 calls) and no other call, and the same change in 4.0.0 makes the history probe pass.

B: narrow the setter's javadoc

Say that the minimum is not applicable to suggestWordBreaks in more-popular mode and that suggestWordCombinations applies it in every mode.

Trade-offs

No behaviour change, but one setting keeps two meanings within one request. Not run (no code change).

C: apply the minimum in both methods

Let suggestWordBreaks keep the configured minimum in more-popular mode (for example Math.max(minSuggestionFrequency, origFreq)) and drop the "Not applicable" sentence.

Trade-offs

Consistent, and matches Solr's reference-guide wording, but it changes word-break results for existing users (row 4 of the table becomes "none"). Not run.

Downstream note

Solr's word-break spell checker maps its minSuggestionFreq and spellcheck.onlyMorePopular options onto these calls, so in one such request word breaks replace the minimum while word joins apply it; its reference guide documents the minimum as a plain floor (from reading Solr's source and docs, not run).

How it was found

Semi-formal: a bounded-exhaustive comparison with executable reference models (2,283,840 calls over small random indexes) found 99,791 calls that differ from a setter-following reference, all from this one check: 73,819 drop a join at a minimum of 2 or 3; 25,972 are minimum-0 calls, not claimed. A later pass reduced it to one JUnit test.

The method in full256 words

Method: bounded-exhaustive translation validation (the hunt's "semi-formal" tier): the stock suggestWordCombinations compared with four executable references, one faithful to the code and others that follow the documentation, through the public API.

  • Scope: 40 random 3-document indexes over the strings of length 1-6 over {a, b} in two fields, all 2,379 term arrays of length 1-3 over the separator and a, b, aa, ab, ba, bb in fields f and g, 3 modes, minimum 0-3, maxChanges 1-2: 2,283,840 calls.
  • Result: the jar equals the code-faithful reference on every call and differs from the setter-following reference on 99,791 calls (73,819 calls drop joins at a minimum of 2 or 3, 21,013 at 2 and 52,806 at 3; the other 25,972 suggest docFreq-0 joins at a minimum of 0, which is not claimed). With the one-line fix alone it equals that reference on every call and changes no other call. The comparison also separates a different bug in the same method (the joined word is looked up in the first term's field), which has its own fix. The model is exhaustive over these term arrays and settings, not over all indexes.
  • An earlier pass had built a reference for this method, had to make it apply the minimum to match the code, noticed that the setter says the opposite and set the lead aside as likely a javadoc fix of low value. A later pass re-ran it on main and reduced it to a public-API JUnit test, later minimized to a one-word index.

Prior reports, history and checks

Prior reports

No prior report found (two 51-query scripted searches plus manual searches of Lucene and Solr issues, PRs, commits, JIRA, mailing lists and the web, 2026-11-02). Nearest: #7299 (sort order of word-break suggestions), #12077 and #12100 (other word-break rules).

Searches4 searches

No public issue, PR, JIRA ticket, list thread or commit found in the searches below reports or discusses this rule. Nearest items, none the same bug: #7299 / LUCENE-6237 (open, sort order of word-break suggestions); #12077 and #12100 (other suggestWordBreaks rules); #4597 / LUCENE-3523 (added the class; nothing on this rule); SOLR-2993 (the class author describes the only-more-popular join rule with no minimum); open PR #16847 (docFreq types only, keeps the check). Limits: first page of GitHub results per query; keyword-based list and JIRA search.

What was searched, when, and what turned up
WhereWhenHowFound
apache/lucene issues, PRs and commits; dev@, java-user@, issues@, commits@; JIRA; downstream; web2026-11-02scripted prior-report search, two runs of 51 queries eachno report; nearest #7299 and #12077 (read, not the same)
GitHub (apache/lucene, apache/solr, Lucene.NET, other downstream engines)2026-11-02minSuggestionFrequency, suggestWordCombinations, SUGGEST_MORE_POPULAR, WordBreakSpellChecker; changed files of all 411 open apache/lucene PRsno report; only open #16847 touches the file (docFreq types)
ASF JIRA (SOLR, LUCENE), six lists on lists.apache.org2026-11-02minSuggestionFreq, minSuggestionFrequency, suggestWordCombinations, onlyMorePopular variants0 hits on the rule; SOLR-2993 read as background
full git history2026-11-02git log -S on the check and on the setter sentenceon main's history only the commit that added the class (for the setter sentence also a 2021 reformat); with --all also the same-day branch_4x commit 99fa008a71e and a 2021 branch_8x note commit

History

Present since the class's first commit, 642b882d5a5 (LUCENE-3523, 2012-05-30); first released in 4.0.0-ALPHA. A public-API probe fails on 17 versions from 4.0.0-ALPHA to 10.6.0 and main; in 4.0.0, changing only this check makes it pass. 3.6.x has no such class.

Versions tested6 rows

Introduced by 642b882d5a50721ab88ade17b06cd9e2927745b1 "LUCENE-3523: Add WordBreakSpellChecker to suggest based on word combinations and/or breaks" (James Dyer, 2012-05-30, svn r1344318; JIRA LUCENE-3523, now #4597; no PR) and its same-day branch_4x commit 99fa008a71e, which 4.0.0-ALPHA ships. That commit already contains the setter sentence, the suggestWordBreaks replacement and the unconditional check; no later commit changed their logic. Not a regression. All 124 release tags from 4.0.0-alpha to 10.6.0 in the history clone contain the three pieces (8.8.2 and 8.9.0-8.11.x are not tagged there; 8.11.4 was run); 10.6.0 differs from main in this file only by a comment typo. Upstream main e1faeba08bd (2026-11-02) has the same file as 645d4fe14a20.

Versions and branches executed, with results
Version or commitVerdictResult
main 645d4fe14a20 affectedthe repro fails 1 of 1 through gradle and the standalone runner, assertions on and off
10.6.0, 9.12.3, 9.1.0 affectedthe repro (this exact file) fails 1 of 1 on the release jars
4.0.0-ALPHA to 10.5.2 (15 releases) public-API probeminimum 1 returns [sunflower], minimum 2 returns []: 4.0.0-ALPHA, 4.0.0, 4.10.4, 5.0.0, 5.5.5, 6.0.0, 6.6.6, 7.0.0, 7.7.3, 8.0.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 (also 10.6.0 and main)
4.0.0 with the fix causalitychanging only the check makes the probe return [sunflower]
3.6.0, 3.6.1, 3.6.2 API absentno WordBreakSpellChecker
fix A on 645d4fe14a20 not affectedthe whole suggest module plus the repro: 237 tests, 0 failures, assertions on and off

Independent checks

An independent reproducer, cross-model reviews, an intended-behaviour devil's advocate and an adjudication found it real; judge confidence medium (per the devil's advocate and the adjudication, the fix direction is open, not the facts). A credibility audit confirmed it as a minor consistency defect. Every run fails on main, assertions on and off, and passes with the fix.

Checks and verdicts318 words

Each check worked on its own; the registration check re-ran the test with fresh seeds, and this page re-ran it through gradle.

Each independent check and its verdict
CheckVerdictWhat it found
finder (exhaustive comparison, JUnit) reproducedJUnit fails on main (-ea, -da), 10.6.0, 10.5.2 and 9.12.3; with the fix it passes and upstream TestWordBreakSpellChecker passes 5 of 5.
cross-model review, refute lens real, highAn independent reviewer from another model family (OpenAI GPT-5.6), executing: its own test (red, wood, redwood at docFreq 2, 1, 2, minimum 3) fails with -ea and -da; the nearest controls pass; intended argument moderate.
independent reproducer realOwn test (rain, bow, rainbow at docFreq 2, minimum 3) fails on main (-ea, -da), 10.6.0, 10.5.2, 9.12.3; with the default minimum the join is returned and suggestWordBreaks replaces the minimum in that mode; passes with the fix.
cross-model review, intended lens real, highOpenAI GPT-5.6, executing: own test fails with -ea and -da and on 10.5.2, passes with the fix; intended argument moderate (a global-floor reading; upstream could narrow the javadoc).
intended-behaviour devil's advocate real, moderate, LowBuilt the floor and DirectSpellChecker case and showed why it fails; it judged the minimum-0 case not a bug and the Solr reach modest.
adjudication confirmed, mediumRe-ran the repro with a new seed, -ea and -da, both fail.
credibility audit minor, LowReal, but the dropped word is filtered by the caller's own minimum, so it is a consistency defect, not wrong search results; the repro was then minimized to one word in one document.
this page's runs validatedGradle on stock and with the fix (module tests and lint), re-run with the reworded patch comment; this exact test file on the 10.6.0, 9.12.3 and 9.1.0 release jars and on main through the standalone runner; the controls behind the table; A on top of open PR #16847.

Diagram file

The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes1,382 words

Finding record

LFV-1072, snapshot of 2026-11-06

Registry title
WordBreakSpellChecker.suggestWordCombinations still applies minSuggestionFrequency under SUGGEST_MORE_POPULAR, although the setter says it does not apply in that mode (suggestWordBreaks follows the setter), so valid combined words are silently dropped
Area
suggest / spell (WordBreakSpellChecker word combinations)
Priority
Low, rank 454: In every release since 4.0, WordBreakSpellChecker word combinations still apply a non-default minimum suggestion frequency under SUGGEST_MORE_POPULAR, although the setter says it does not apply there, and silently drop valid combined words. It needs that setting pair, which downstream spell-check settings can produce, and mostly affects rare words, so it is Low.
Impact class
minor
Impact note
Hits lucene-suggest users who give WordBreakSpellChecker a non-default minSuggestionFrequency (2 or more) and ask suggestWordCombinations for SUGGEST_MORE_POPULAR suggestions, directly or through a downstream word-break spellcheck component that maps a minimum-frequency parameter and an only-more-popular option onto these calls: a combined word that is at least as frequent as its most popular part but rarer than the configured minimum is silently not suggested, deterministically, at any index size, with -ea and -da, in every release since 4.0.0-ALPHA; the default minimum (1) and the other suggest modes are unaffected, nothing throws, and no index or query is harmed.
Summary
You configure a WordBreakSpellChecker with setMinSuggestionFrequency(2), whose javadoc says the minimum is 'Not applicable when used with SUGGEST_MORE_POPULAR', and ask suggestWordCombinations for SUGGEST_MORE_POPULAR suggestions; you expect every indexed combined word that is at least as frequent as its most popular part, which is the method's own MORE_POPULAR rule and what suggestWordBreaks does with the same setting. Instead the minimum still filters combinations: with sun, flower and sunflower in one document (docFreq 1 each), suggestWordCombinations([f:sun, f:flower], 5, reader, SUGGEST_MORE_POPULAR) returns no suggestion instead of sunflower. The cause is the check combinedTermFreq >= minSuggestionFrequency (WordBreakSpellChecker.java:208), which runs after the MORE_POPULAR rule in every mode.
Introduced
642b882d5a50721ab88ade17b06cd9e2927745b1 (trunk, svn r1344318, 'LUCENE-3523: Add WordBreakSpellChecker to suggest based on word combinations and/or breaks', by jdyer1; JIRA LUCENE-3523 = apache/lucene#4597; svn era, no pull request) and its same-day branch_4x commit 99fa008a71ef1600bf4217824f7786682432a48c (svn r1344329, the code 4.0.0-ALPHA ships). The class was born with all three pieces of the inconsistency: the setter javadoc 'Not applicable when used with SUGGEST_MORE_POPULAR', the suggestWordBreaks substitution useMinSuggestionFrequency = (origFreq == 0 ? 1 : origFreq) under SUGGEST_MORE_POPULAR, and the unconditional combinedTermFreq >= minSuggestionFrequency check in suggestWordCombinations (main :208, 4.0.0 :232) [read: git blame and git log -S in the history clone, which attribute :208 to 642b882d5a5, and :206 and :414 to it with --ignore-rev 2cbf261032d (the 2021 reformat); exec: 4.0.0 with only that check changed passes the probe]. No later commit changed these lines (LUCENE-4587, #12077, #12100, #13328, #16255 and formatting commits touch other code). Not a regression. (2012-05-30 (trunk 642b882d5a5 at 15:58Z; branch_4x 99fa008a71e at 16:10Z))
First affected version
4.0.0-ALPHA [exec: the version-portable public-API probe reports the bug with -ea and -da, plain classpath, jars SHA-1-checked against Maven Central]; first GA release 4.0.0 [exec, -ea and -da]; no earlier release has the API (3.6.0, 3.6.1 and 3.6.2 have no WordBreakSpellChecker [exec: jar inspection])
Affected versions
Every release that has WordBreakSpellChecker, 4.0.0-ALPHA through 10.6.0 (released 2026-10-29, 4ae946e5a9b), and main 645d4fe14a20 (unreleased 11.0); not fixed. [exec] the one public-API probe (one document with sun, flower, sunflower; control minSuggestionFrequency 1 returns [sunflower], probe 2 returns []) fails on 4.0.0-ALPHA, 4.0.0, 4.10.4, 5.0.0, 5.5.5, 6.0.0, 6.6.6, 7.0.0, 7.7.3, 8.0.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2, 10.6.0 and main with -ea, and with -da on 4.0.0-ALPHA, 4.0.0, 10.6.0 and main; the one-test JUnit fails on main (-ea and -da), 10.6.0, 10.5.2 and 9.12.3 (9.x port: only the CombineSuggestion field access differs, the class became a record in 10.0 via #13328). [read] all 124 release tags 4.0.0-alpha..10.6.0 in the history clone contain the line-208 check, the setter javadoc and the suggestWordBreaks substitution (8.8.2 and 8.9.0-8.11.x are untagged in the clone; 8.11.4 was executed). Upstream main e1faeba08bd (2026-11-02) has a WordBreakSpellChecker.java byte-identical to 645d4fe14a20; branch_10_6 has the 10.6.0 copy, which differs from main only by a comment typo. Not new in 10.6. Executed on Linux aarch64 with JDK 25.0.3 only.
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
Found by
semi-formal; also reached by exhaustive check
Upstream and prior reports
none found (searched 2026-11-02; the finder's, the independent reproduction's, the intended-behaviour check's and the adjudication's searches agree): two scripted prior-report searches of 51 queries each (GitHub issues, PRs and commits on apache/lucene, downstream repositories, the dev@, java-user@, issues@ and commits@ lists, JIRA and the web; 19 and 50 hits, none high), manual GitHub searches over apache/lucene, apache/solr, Lucene.NET and downstream engines (minSuggestionFrequency, suggestWordCombinations, SUGGEST_MORE_POPULAR, WordBreakSpellChecker and several phrasings), JIRA REST over SOLR and LUCENE (minSuggestionFreq, minSuggestionFrequency, suggestWordCombinations: 0 each; the descriptions and comments of 10 related tickets grepped), lists.apache.org over six Lucene and Solr lists, web searches, git log -S over the full history, and the changed files of all 411 open apache/lucene PRs (only #16847, int to long docFreq types, touches the file and keeps line 208). Nearest items, none the same bug: #7299 / LUCENE-6237 (open, sort order of word-break suggestions) and its 2015 solr-user@ thread, #12077 and #12100 (suggestWordBreaks), #4597 / LUCENE-3523 (introduced the class; 9 comments, no discussion of the rule), #5652 / LUCENE-4587, #6557 / LUCENE-5494, #4510 / LUCENE-3436 (adds SuggestMode), SOLR-2993 (Solr integration; the class author describes the only-more-popular combination rule with no minimum frequency), SOLR-10263, SOLR-8010, SOLR-6297, SOLR-5191, SOLR-4115, SOLR-8175, SOLR-12284, SOLR-4489. Refresh (2026-11-02): 'minSuggestionFrequency' and 'suggestWordCombinations' return 0 apache/lucene issues or PRs, 'WordBreakSpellChecker' only #7299, #12100, #12077, #4597 and #5652, 'SUGGEST_MORE_POPULAR' only #4510; the 17 apache/lucene issues and PRs updated since 2026-10-31 are unrelated, and none of the 15 PRs among them changes lucene/suggest spell files; open PRs matching WordBreakSpellChecker: none, spellchecker: only #16957 (Albanian analyzer); apache/solr 'minSuggestionFreq': 0; the newest commit to the file is still 54283150a81 (author date 2026-06-14, typos-cli, no logic change) on main and 942065cc3e7 (author date 2024-09-10) on branch_10_6. Not fixed upstream: main e1faeba08bd has the same blob as 645d4fe14a20. Gaps: GitHub search reads the first page per query; list and JIRA search is keyword-based; private reports are not visible.
Lucene commit tested
main 645d4fe14a20 (gradle in a scratch worktree: the repro on stock with assertions on and off; the fix with the whole suggest module, assertions on and off, plus :lucene:suggest:check -x test, re-run after the patch comment was reworded); the 10.6.0, 9.12.3 and 9.1.0 release jars (the repro, this exact file); a public-API probe on 17 versions from 4.0.0-ALPHA to 10.6.0 and main, and 4.0.0 with the fix (the hunt). OpenJDK 25.0.3, Linux aarch64. Upstream main e1faeba08bd (2026-11-02) has the same WordBreakSpellChecker.java.

Lucene and upstream sources

Finding record
  • lucene/suggest/src/java/org/apache/lucene/search/spell/WordBreakSpellChecker.java:98-107 (at 645d4fe14a20) (suggestWordBreaks: under SUGGEST_MORE_POPULAR useMinSuggestionFrequency = (origFreq == 0 ? 1 : origFreq)), :142-143 (suggestWordCombinations javadoc: 'When suggestMode equals SUGGEST_MORE_POPULAR, each suggestion will have the same, or better frequency than the most-popular included term'), :203-204 (combined term docFreq), :206 (MORE_POPULAR rule combinedTermFreq >= maxFreq), :208 (combinedTermFreq >= minSuggestionFrequency in every mode; fix site), :413-416 (setMinSuggestionFrequency javadoc 'The minimum frequency a term must have to be included as part of a suggestion. Default=1 Not applicable when used with SUGGEST_MORE_POPULAR')
  • GitHub, read-only (2026-11-02): apache/lucene #4597, #4510, #5652, #6557, #7299, #12077, #12100, #16847, #16957 and the issues and PRs updated since 2026-10-31 with their changed files; repos/apache/lucene/commits?path=lucene/suggest/src/java/org/apache/lucene/search/spell/WordBreakSpellChecker.java on main and branch_10_6 (author dates); repos/apache/lucene/contents/lucene/suggest/src/java/org/apache/lucene/search/spell/WordBreakSpellChecker.java?ref=main (blob 8f56dd57513, :106, :208, :414-415); apache/solr search 'minSuggestionFreq'
The finding in 30 seconds
  • a Lucene main checkout lucene/suggest/src/java/org/apache/lucene/search/spell/WordBreakSpellChecker.java:34, 104-107, 142-143, 155-161, 178-183, 198-208, 413-421
  • a Lucene main checkout lucene/suggest/src/java/org/apache/lucene/search/spell/SuggestMode.java:28
Why it looks unintended
  • a Lucene main checkout lucene/suggest/src/java/org/apache/lucene/search/spell/WordBreakSpellChecker.java:104-107, 142-143, 206-208, 413-416
  • a Lucene main checkout lucene/suggest/src/java/org/apache/lucene/search/spell/SuggestMode.java:22, 28
  • a Lucene main checkout lucene/suggest/src/java/org/apache/lucene/search/spell/DirectSpellChecker.java:166-178, 350-356
  • a Lucene main checkout lucene/suggest/src/test/org/apache/lucene/search/spell/TestWordBreakSpellChecker.java:178, 199, 428 (no SUGGEST_MORE_POPULAR anywhere in the file)
History
  • a Lucene clone tag releases/lucene/10.6.0 (WordBreakSpellChecker.java lines 106, 208, 414-415)

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.