UnifiedHighlighter.Builder#withFlags is silently ignored: the highlighter always takes its flags from the four boolean builder options

Finding LFV-288Area: highlighter / UnifiedHighlighter builder and highlight flags (UnifiedHighlighter.evaluateFlags(Builder) never reads Builder.flags)Priority: lowImpact: minorNovelty: new

Finding page for LFV-288

AI-assisted finding, confirmed by automated checks. Upstream status: Fixed, by Christoph Büscher (PR #16983, approved by Luca Cavanna; an independent fix, later than our listing). Fixed on main by #16983 (merged 2026-11-04, milestone 11.0.0); the PR does not cite this list. Not backported to branch_10_6 as of 2026-11-04. Novelty stays new (owner decision 2026-11-04: listed publicly 2026-10-27, the fix came later and independently). Source. This site files nothing upstream.

What it shows

You build a UnifiedHighlighter with Builder#withFlags(EnumSet.of(MULTI_TERM_QUERY)), whose javadoc says the set 'will override' the four boolean builder options, and expect it to behave like the same flags chosen through those options: for a PhraseQuery 'alpha beta' on 'alpha beta. alpha gamma.' that gives '<b>alpha</b> <b>beta</b>. <b>alpha</b> gamma.'. Instead the constructor's evaluateFlags(Builder) tests the highlighter's own flags field, which is still null at that point, rather than the builder's, so the set passed to withFlags is never read: getFlags returns the defaults [PHRASES, MULTI_TERM_QUERY, PASSAGE_RELEVANCY_OVER_SPEED, WEIGHT_MATCHES] and the phrase is highlighted strictly as '<b>alpha beta</b>. alpha gamma.'. Every set is ignored the same way, including the empty set (a PrefixQuery is still highlighted), with or without assertions.

The test shows that UnifiedHighlighter.Builder#withFlags is ignored: with withFlags(EnumSet.of(MULTI_TERM_QUERY)) the highlighter still uses the default flags (strict phrase highlighting).

Expected vs actual

Expected <b>alpha</b> <b>beta</b> <b>alpha</b> gamma for PhraseQuery "alpha beta" on alpha beta alpha gamma; actual: <b>alpha beta</b> alpha gamma, with -ea and -da.

Files

Run it

# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble   (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:$LUCENE/lucene/highlighter/build/libs/lucene-highlighter-$V.jar
CP=$CP:$LUCENE/lucene/memory/build/libs/lucene-memory-$V.jar
CP=$CP:$LUCENE/lucene/queries/build/libs/lucene-queries-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar   # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar   # JUnit Platform console launcher (Maven Central)

javac -d classes -cp "$CP" $(find src -name '*.java')

# run 1: assertions on, seed DEADBEEF
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=DEADBEEF \
     -cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
     execute --select-class org.apache.lucene.search.uhighlight.TestUnifiedHighlighterBuilderWithFlags

Gradle alternative: copy the test into the matching module's src/test/ tree of the checkout and run ./gradlew -p lucene/<module> test --tests <class> -Ptests.seed=<seed> (add -Ptests.asserts=false for a run without assertions).

Feedback

Open an issue in lucene-ai-bug-hunt. Please do not open issues or pull requests in apache/lucene about this finding unless a Lucene committer agrees it should be filed.

Snapshot of 2026-11-04. AI-assisted; triaged by a Lucene maintainer as noted above. Feedback: open an issue in lucene-ai-bug-hunt.