All findings · Lucene finding · LFV-667 · bengali-khio-rule-checks-vowel-sign-i

BengaliNormalizer's khio rule tests the wrong character, so the conjunct ক্ষ is never folded

Lucene's Bengali normalizer has a "Khio" rule that should rewrite the conjunct ক্ষ as খ, as the paper it cites does, but it tests U+09BF (a vowel sign) instead of U+09B7 (ষ), so it never fires on ক্ষ: ক্ষমা is indexed as ক্সম, not খম like খমা. Present since 7.1.0.

Summary

Priority
Low, rank 296A relevance-only normalization typo (impact class minor) in one language analyzer, whose fix changes indexed terms.
Impact
minor
Since
7.1.0 (LUCENE-7940, 2017), the first release with Bengali analysisintroduced 2017-08-24 · about 9 years
Affected
7.1.0 to 10.5.2 and main
Status
documented
Found by
bounded-exhaustive check of the normalizer against its documented rules

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

Bengali writes the conjunct ক্ষ (ক + ্ + ষ), found in everyday words such as ক্ষমা, as one joined letter; the paper the normalizer cites says it sounds like খ at the start of a word. Lucene's Bengali normalizer has a rule to rewrite it as খ, but the rule checks for the wrong third character.

Three panels. Panel 1, the rule and the code: BengaliNormalizer.java lines 56 to 58, the comment Khio (Ka + Hoshonto + Murdorno Sh) and the condition that tests U+09BF; the conjunct as written is KA U+0995, HASANTA U+09CD, SSA U+09B7, while line 58 asks for KA, HASANTA, VOWEL SIGN I U+09BF, which is never true for a real word. Panel 2, the filter output on stock main versus the documented rule: ক্ষমা gives ক্সমা instead of খমা, পরীক্ষা gives পরিক্সা instead of পরিকখা, and the malformed ক্িমা gives খমা instead of staying unchanged. Panel 3, the effect on search: BengaliAnalyzer indexes ক্ষমা as ক্সম and খমা as খম, so the query খমা finds only one of the two documents; the fix changes U+09BF to U+09B7 on line 58, after which both documents match and the upstream Bengali tests pass; it changes indexed terms, so fields need reindexing, and deleting the rule is the alternative.
Left: line 58 asks for ি (U+09BF) where the conjunct ক্ষ has ষ (U+09B7). Right: the filter's output, documented versus stock. Bottom: ক্ষমা and খমা get different terms, and the one-character fix. Select the still to open it at full size. Generated with AI, checked against the code.
  1. Index Bengali text with the stock new BengaliAnalyzer() (or BengaliNormalizationFilter). Many words contain the conjunct ক্ষ, which is KA + HASANTA + SSA: ক্ষমা (forgiveness), পরীক্ষা (exam), শিক্ষা (education).
  2. The normalizer's rule // Khio (Ka + Hoshonto + Murdorno Sh) follows the paper its javadoc cites: ক্ষ becomes খ at the start of a word and কখ elsewhere. You expect ক্ষমা to become খমা and পরীক্ষা to become পরিকখা, so ক্ষমা and খমা both index as খম.
  3. You get ক্ষমা → ক্সমা and পরীক্ষা → পরিক্সা. The condition compares the third character with U+09BF (vowel sign ি) instead of U+09B7 (ষ), one hex digit off. So the rule never fires, and only the separate rule ষ → স applies. The analyzer indexes ক্সম for ক্ষমা but খম for খমা.
  4. Instead, the rule fires on KA + HASANTA + VOWEL SIGN I, a sequence that well-formed text never contains: ক্িমা becomes খমা. There is no error, identical spellings still match each other (same analyzer version), and the result is the same with assertions on and off.

Who is affected

Every application that indexes Bengali with the stock BengaliAnalyzer, BengaliNormalizationFilter or the bengaliNormalization factory, on every release since 7.1.0. Every word with ক্ষ gets a term the rule would not produce, so the same word spelled with খ does not match it, and বক্ষ (chest) shares a term with বক্স (box). Identical spellings still match (same analyzer version).

Mechanism, terms and numbers481 words

BengaliNormalizer (package-private; used by BengaliNormalizationFilter, by BengaliAnalyzer at index and query time and by the bengaliNormalization factory) says it implements the algorithm of the paper "A Double Metaphone encoding for Bangla and its application in spelling checker" (UzZaman and Khan). Its rule // Khio (Ka + Hoshonto + Murdorno Sh) rewrites the conjunct ক্ষ (KA + HASANTA + SSA) as খ at the start of a word and as কখ elsewhere, mirroring the paper's two khio cases. But the condition tests the third character against '\u09BF' (BENGALI VOWEL SIGN I) instead of '\u09B7' (BENGALI LETTER SSA). So the rule never fires on correctly encoded ক্ষ: ক্ষমা normalizes to ক্সমা (the separate "All sh" rule turns ষ into স), and BengaliAnalyzer indexes it as ক্সম, while খমা is indexed as খম. The rule fires instead on the malformed KA + HASANTA + VOWEL SIGN I (ক্িমা becomes খমা). Every release from 7.1.0 to 10.5.2 and main has it (63 release tags read, 16 releases run). The fix is one character, and it changes the indexed term of every word with ক্ষ, so affected fields need a reindex; deleting the rule is the alternative that keeps today's terms.

Terms

  • ক্ষ (khio): the conjunct KA U+0995 + HASANTA U+09CD + SSA U+09B7. A hasanta (virama) joins two consonants; a vowel sign such as ি (U+09BF) follows a consonant, never a hasanta, in well-formed text.
  • BengaliNormalizer: a left-to-right rewrite of each token, one rule per character (switch (s[i])). Its javadoc says it implements the cited paper. It is package-private and is reached through BengaliNormalizationFilter, which BengaliAnalyzer uses at index time and in normalize(), and through the bengaliNormalization factory.

The bug

  1. The rule is case '\u0995': under // Khio (Ka + Hoshonto + Murdorno Sh) (BengaliNormalizer.java:56-68). "Murdorno Sh" is ষ, U+09B7.
  2. Its condition is s[i + 1] == '\u09CD' && s[i + 2] == '\u09BF' (:58). U+09BF is BENGALI VOWEL SIGN I, the code point that the DirghoI rule writes two cases earlier (:48).
  3. Its branches are right: word-initial gives খ, otherwise কখ (:59-66), the paper's two cases.
  4. On correctly encoded ক্ষ the branch never matches, so the later "All sh" rule turns ষ into স (:125-129): ক্ষ ends as ক্স. On the malformed ক্ি the branch does fire and folds it to খ (or কখ), dropping the vowel sign.

The numbers

inputas documentedstock filterstock BengaliAnalyzer term
ক্ষমাখমাক্সমাক্সম (খমা gives খম)
পরীক্ষাপরিকখাপরিক্সাপরিক্স
শিক্ষাসিকখাসিক্সাসিক্স
বক্ষ (chest)বকখবক্সবক্স, the same as বক্স (box)
ক্িমা (malformed)unchanged (not a khio input)খমাখম

The hunt's bounded check (all 551,881 strings of length 0 to 4 over 27 rule characters) found the khio rule violated on all 28 inputs that start with ক্ষ.

Exposure and likelihood364 words
  • Who is exposed. BengaliAnalyzer (the default constructor included), BengaliNormalizationFilter, and the bengaliNormalization factory, for example in a CustomAnalyzer. No setting switches this rule alone; only keyword-marked tokens skip it, together with the whole normalizer (BengaliNormalizationFilter.java:48-49), for example words in BengaliAnalyzer's stem-exclusion set (BengaliAnalyzer.java:123-125) or after a SetKeywordMarkerFilter, as the filter javadoc suggests.
  • Ordinary input. ক্ষ occurs in everyday words: ক্ষমা, পরীক্ষা, শিক্ষা, রক্ষা, বৃক্ষ, দক্ষিণ and many more. Every such word gets a term the rule would not produce, at index and query time alike.
  • What the damage looks like. Silent: no error, and identical spellings still match (with the same analyzer version at index and query time). What fails is the match the rule exists for. In a six-document index (ক্ষমা, খমা, ক্সমা, ক্িমা, বক্ষ, বক্স) the term query for ক্ষমা matches ক্ষমা and ক্সমা, the one for খমা matches খমা and ক্িমা, and বক্ষ matches বক্ষ and বক্স. With the fix, ক্ষমা and খমা both match ক্ষমা and খমা, and বক্ষ matches only itself.
  • Both directions. Over 1,508,597 generated short strings (a synthetic set, not a corpus), the bug loses 36,270 conflation pairs that the rule would create and keeps 40,223 that the fix removes; the fix changes the output of 1,804 strings, each containing ক্ষ (902) or the malformed ক্ি (902).
  • Scale. One word reproduces it; assertions on and off behave the same.
  • Reindex. A fix that makes the rule fire changes the term of every ক্ষ word, so fields analyzed before the fix keep the old terms until they are reindexed. Until then even identical text misses: the document ক্ষমা indexed by the old code has the term ক্সম, and the query ক্ষমা analyzed with the fix looks for খম.
  • What is not shown. The evidence is a mismatch between the code and its own comment and cited paper. No corpus evaluation or native-speaker check was done, so nothing here shows that the fold improves Bengali search, or that খমা is a spelling in actual use for ক্ষমা rather than the one the paper's phonetic code equates with it.

Minimal repro

The test shows that BengaliNormalizer's "Khio (Ka + Hoshonto + Murdorno Sh)" rule checks U+09BF (vowel sign I) instead of U+09B7 (SSA), so it never folds the conjunct ক্ষ.

TestBengaliKhioRule.java (JUnit test)29 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.analysis.bn;

import org.apache.lucene.tests.analysis.BaseTokenStreamTestCase;

public class TestBengaliKhioRule extends BaseTokenStreamTestCase {

  public void testWordInitialKhio() throws Exception {
    // Khio rule: KA HASANTA SSA MA AA must become KHA MA AA; actual: KA HASANTA SA MA AA
    assertTokenStreamContents(
        new BengaliNormalizationFilter(whitespaceMockTokenizer("ক্ষমা")),
        new String[] {"খমা"});
  }
}
Command
# in a copy of this finding's repro folder (repro/LFV-667/, which holds src/)
# 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/analysis/common/build/libs/lucene-analysis-common-$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.analysis.bn.TestBengaliKhioRule
Expected vs actual
Expected: ক্ষমা (U+0995 U+09CD U+09B7 U+09AE U+09BE) normalizes to খমা (U+0996 U+09AE U+09BE). Actual (-ea and -da): ক্সমা (U+0995 U+09CD U+09B8 U+09AE U+09BE).

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

More files, environment, full output38 words

Environment

luceneLucene main e1faeba08bde (2026-11-02)
jdk25.0.3+9-LTS

Observed output

# Lucene main 645d4fe14a20, assertions on (-ea), seed DEADBEEF: 1 test(s) run, 1 failed
    => org.junit.ComparisonFailure: term 0 expected:<[খ]মা> but was:<[ক্স]মা>
       org.junit.Assert.assertEquals(Assert.java:117)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:262)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:513)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:598)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:622)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:689)
       org.apache.lucene.analysis.bn.TestBengaliKhioRule.testWordInitialKhio(TestBengaliKhioRule.java:25)

Why it looks unintended

  • The rule's comment names KA + HASANTA + SSA, and its two branches are the two khio cases of the paper the class cites.
  • U+09BF is a vowel sign, which never follows a hasanta in well-formed text: the rule can only fire on malformed input.
  • No test, CHANGES entry or discussion mentions khio; since 2017 the line was only reformatted.
The full argument323 words
  • The contract. The comment at BengaliNormalizer.java:56 names the rule, and the class javadoc (:24-26) says the class implements the cited paper. The paper lists the khio conjunct as \u0995 \u09CD \u09B7 with "Case 1: In the initial position of a word ... it is given the same code as" KHA and "Case 2: In the middle or at the end of words, it is similar to" KA KHA. The code's branches mirror these two cases (the paper assigns phonetic codes for a spelling checker; the code applies them as character rewrites); only the third code point differs. The finder, the independent reproducer and the devil's advocate each extracted the paper text.
  • No contract names the coded sequence. Neither the comment nor the paper, nor any test, CHANGES entry or discussion, names KA + HASANTA + VOWEL SIGN I; the only mention found is the outside wiki page (see the prior reports), which transcribes the code. The sequence does not occur in well-formed Bengali, and the reviewed history and discussions identify no damage pattern that rewriting it as খ (dropping the vowel sign) would repair.
  • Tests. TestBengaliNormalizer tests every other commented rule and has no khio case (nor one for the uncommented ৎ-to-ত rule, BengaliNormalizer.java:142-144), so no test pins the current khio output. The 4 Bengali suites pass with either fix.
  • History. The rule came with the contributor's first commit 1bca06b8a938 (2017-08-24, LUCENE-7940, now gh#8989; apache/lucene-solr PR #238); later commits on these lines (8ef6a0da568, f99deb58b74) only reformat. The review never discussed khio.
  • Precedent. Lucene changes analysis output when it is wrong and asks users to reindex (MIGRATE.md, "Romanian analysis", 10.0), and fixed missing Telugu normalization as a bug (GITHUB#13059).
  • No public javadoc for the mapping. The normalizer is package-private; the public filter says only that it normalizes "the orthography" and that "the normalization may cause unrelated terms to conflate". The contract is the comment plus the cited paper.
The devil's-advocate check227 words

The strongest "intended" case (the devil's-advocate verifier and the intended-lens review by another model family, rated moderate): no public javadoc names the mapping; no test covers khio; the output has been stable for 9 years, and the original review measured relevance with this code; a maintainer, rmuir, defends the Indic folds in gh#14659 as intended for damaged text from legacy fonts and PDF extraction ("The filter here is working as documented ... Just don't use the filter if you dont want the transformations that it does."); and another contributor's wiki page reports that a native-speaker reviewer rejected the filter's phonetic rules for search, so a dead khio rule may be "accidentally better".

Why it fails: a comment that names the rule plus a javadoc that cites the paper defining it are a stated contract, and both say KA + HASANTA + SSA. Nothing explains why a branch so labelled matches a vowel sign. Long life is not evidence that anyone chose it: no comment, test, CHANGES entry or discussion reviewed mentions khio. And "working as documented" cuts the other way, because the code does not do what is documented. Verdict: a defect (adjudication: confirmed, high). What the counter-case gets right: the search value of the fold is debatable, the paper describes phonetic codes for a spelling checker rather than search normalization, and a fix changes indexed terms.

Suggested fix

Recommended Test U+09B7 (SSA), as the comment and the paper say

Test SSA, as the comment says: change '\u09BF' to '\u09B7' in BengaliNormalizer.java:58. The repro and all 1,912 tests of the module pass. The fix changes the term of every ক্ষ word, so affected fields need a reindex; deleting the rule is the alternative that keeps today's terms.

// Khio (Ka + Hoshonto + Murdorno Sh)
case '\u0995':
  if (i + 2 < len && s[i + 1] == '\u09CD' && s[i + 2] == '\u09B7') {
All options and trade-offs3 options · 481 words

Two options were built and checked; maintainers may prefer either. Add a khio case to TestBengaliNormalizer with whichever is chosen.

Test U+09B7 (SSA), as the comment and the paper say (validated by the checks) (recommended)

One character at BengaliNormalizer.java:58. Then ক্ষমা → খমা, পরীক্ষা → পরিকখা, বক্ষ → বকখ (no longer the same as বক্স), and the malformed ক্ি is left alone.

// Khio (Ka + Hoshonto + Murdorno Sh)
case '\u0995':
  if (i + 2 < len && s[i + 1] == '\u09CD' && s[i + 2] == '\u09B7') {

Trade-offs

  • Changes the indexed term of every word with ক্ষ: needs a CHANGES entry and a MIGRATE note (reindex fields analyzed with BengaliAnalyzer, BengaliNormalizationFilter or the bengaliNormalization factory), as the Romanian change did in 10.0. Main is the natural target; a 10.x backport is debatable.
  • The filter folds more: word-initial ক্ষ words merge with খ words, as the paper intends and as the filter javadoc's conflation warning covers.
  • Same complexity and branch body, but the work moves from ক্ি inputs to ক্ষ inputs: on one repeated 96,000-character token (far beyond StandardTokenizer's default 255-character limit) stock takes about 0 ms on ক্ষ and 62 ms on ক্ি, and the fix the reverse; negligible for word-length tokens.

What the checks validated

  • Scratch worktree at a9779ef3c193, gradle, JDK 25: the new test fails 3 of 3 on stock (assertions on and off) and passes with the fix; org.apache.lucene.analysis.bn.*: 5 suites, 25 tests (22 upstream + 3 new), 0 failures, seed 5EED0667 with assertions on and 5EED0668 with them off; :lucene:analysis:common:checkGoogleJavaFormat passes; the whole :lucene:analysis:common:test: 340 suites, 1,912 tests, 18 skipped, 0 failures.
  • Standalone runner with the fix shadowed: the new test 3 of 3 pass with -ea and -da; earlier, the finder's test 3 of 3 and the 4 upstream suites 22 of 22; the release probe passes on 7.1.0 with the fix.

Delete the rule and its comment (validated by the checks)

Remove BengaliNormalizer.java:56-69. Well-formed text keeps today's terms (ক্ষমা stays ক্সমা); only the malformed ক্ি stops being folded.

Trade-offs

  • No reindex for well-formed text (only an indexed malformed ক্ি gets a new term), and the code no longer claims a rule that it does not implement.
  • Gives up the paper's khio fold and keeps the বক্ষ/বক্স collision. Fits the view that the Indic folds are already aggressive (gh#14659).

What the checks validated

The 4 upstream Bengali suites, 22 of 22, pass with it (assertions on and off); the khio test fails under it, as designed.

Keep the code and change the comment

Describe what the code does. Not run.

Trade-offs

Keeps every indexed term, so it is the most compatible option; but it documents a rule for a sequence that well-formed text does not contain, keeps the vowel-sign deletion and leaves the paper's khio case unimplemented.

How it was found

A bounded-exhaustive check of the stock Bengali filter against an oracle for each rule its comments document: all 551,881 strings of up to 4 characters over 27 rule characters. The khio oracle failed on all 28 inputs that start with ক্ষ, and reading the condition found the wrong code point.

The method in full156 words

Method: bounded-exhaustive translation validation on the stock main jars (the study's "semi-formal" tier). A hunt unit, "script normalizers against their documented rules", 2026-10-27.

  • For every string of length 0 to 4 over a 27-character alphabet built from the rule characters (consonants, vowel signs, hasanta and the other signs; 551,881 inputs, the empty one included), the output of BengaliNormalizationFilter was checked against a directed oracle per documented rule. The khio oracle (a word-initial ক্ষ must become খ, from the rule comment) failed on all 28 inputs that start with the conjunct.
  • A second relation (ী is documented to map to ি, so swapping one for the other in the input must not change the output) failed on ক্ী (→ ক্ি) versus ক্ি (→ খ), which pointed at U+09BF in the khio condition; reading the code confirmed it.
  • Then an AI finder wrote the public-API JUnit, and independent AI tools reproduced, reviewed and judged it (see the checks).

Prior reports, history and checks

Prior reports

No prior report: 89 tool queries over GitHub, commits, mailing lists, JIRA and the web (106 hits), manual GitHub searches and all 409 open PRs (2026-10-27). Another contributor's wiki page copies the coded rule without flagging it.

Searches4 searches

No issue, PR, commit, JIRA entry, list thread or web page reports that the khio rule tests U+09BF or that ক্ষ is never folded. Upstream main, branch_10x and branch_10_5 still have the condition.

Near-miss, to disclose: another contributor's wiki page of Bengali analysis notes (mediawiki.org, 2022, linked from a comment in gh#14659) lists the coded rule as "ক্ি (ka + virama + i)" under the label "Khio (Ka + Hoshonto + Murdorno Sh)" without noting that the two disagree, and recommends disabling the filter as too aggressive. It describes the code, not a defect.

What was searched, when, and what turned up
WhereWhenHowFound
GitHub issues and PRs (apache/lucene), commits2026-10-2740 issue/PR and 3 commit queries through a scripted prior-report search; a manual search for Bengali (all 12 results read); gh#8989 (30 comments) and lucene-solr PR #238 with review commentsno report; the introducing review never discusses khio
Open PRs2026-10-27the changed files of all 409 open apache/lucene PRsnone touches analysis/bn; #14699 (Telugu IndicNormalizer) is related, not the same
Mailing lists and JIRA2026-10-2732 queries over dev@, java-user@, issues@ and commits@; solr-user@; 4 JQL queriesno report
Downstream repositories, all of GitHub and the web2026-10-272 downstream queries plus manual searches of Solr, Elasticsearch and OpenSearch; issue search for BengaliNormalizer and khio; code search for the comment word Murdorno (about 35 copies, 4 opened); 8 web queriesno report; the 4 copies opened carry the same condition; the wiki page above

History

Present since the contributor's first commit 1bca06b8a938 (2017-08-24, LUCENE-7940), first released in 7.1.0 and never changed except for reformatting. It fails on all 16 releases run from 7.1.0 to 10.5.2 and on main, and the 63 release tags read have the same condition; 7.0.1 has no Bengali API.

Versions tested7 rows

Introduced by 1bca06b8a938 "LUCENE-7490: Added bengali language analyzer" (a typo for LUCENE-7940, now gh#8989; apache/lucene-solr PR #238), 2017-08-24. It was merged on 2017-09-05 into master (08128f712fc) and branch_7x (e18532cb687); CHANGES lists "LUCENE-7940: Add BengaliAnalyzer." under 7.1.0. git blame -w and git log -L put the rule on this commit; later commits only reformat (8ef6a0da568, LUCENE-9570; f99deb58b74, #13661). Not a regression. All 63 release tags in the history clone that have the file contain the same condition, and the 8 releases that have no tag there (8.8.2-8.11.3) fail when run, so every release from 7.1.0 to 10.5.2 is affected.

Versions and branches executed, with results
Version or commitVerdictResult
main a9779ef3c193 affectedthe brief's test 3 of 3 fail (gradle and the standalone runner, assertions on and off)
10.5.2 affectedthe brief's test 3 of 3 fail; the finder's test 3 of 3; the release probe 3 of 3
7.1.0, 7.7.3, 8.0.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0 affectedthe release probe fails 3 of 3 on each (filter and BengaliAnalyzer.normalize); the finder's test also on 9.12.3
8.8.2, 8.9.0, 8.10.0, 8.10.1, 8.11.0, 8.11.1, 8.11.2, 8.11.3 affectedthe release probe fails 3 of 3 on each, assertions on (no tags in the history clone; run after the fact checks)
7.1.0-7.7.3, 8.0.0-8.8.1, 9.0.0-10.5.2 (63 tags) read onlythe same condition
7.0.1 API absentthe probe does not compile: no org.apache.lucene.analysis.bn package
main + fix, 7.1.0 + fix not affectedall repros pass; main: the 5 Bengali suites, 25 tests, 0 failed

Independent checks

An independent reproducer, a devil's advocate, an adjudication and reviews by another model family (which ran their own tests) all found it real; the adjudication confirmed it with high confidence. Registry re-runs and a final audit reproduced it with new seeds.

Checks and verdicts427 words

Every check wrote or ran its own test against the stock jars; all but the intended-lens review also ran with assertions off. This page was then fact-checked independently and by a reviewer from another model family.

Each independent check and its verdict
CheckVerdictWhat it found
finder candidatePublic-API JUnit fails 3 of 3 on main (-ea, -da), 10.5.2 and 9.12.3; the one-character fix passes 3 of 3 and the upstream Bengali suites 22 of 22.
cross-model review, refute lens real (high)An independent reviewer from another model family (OpenAI GPT-5.6) ran its own JUnit: 6 of 12 fail over 3 iterations with -ea, 2 of 4 with -da (ক্ষত gives ক্সত, not খত); passes with the fix.
Independent reproducer realOwn 6-test JUnit fails 4 of 6 on main, 10.5.2 and 9.12.3 (index-time terms included); its 2 controls pass; re-extracted the paper's khio entry.
Cross-model review, intended lens real (high)OpenAI GPT-5.6: the intended-behaviour argument is moderate and rejected; its expected-semantics test fails 3 of 3 on main and passes with the fix.
Devil's advocate realRe-ran the finder's test; its own probe on main and 10.5.2 shows ক্ষ never folded at the start, middle or end, while the malformed ক্ি is folded.
adjudication confirmed (high)Re-ran the independent reproduction's test (4 of 6 fail, -ea and -da), the finder's (3 of 3) and the finder's with the fix (3 of 3 pass); class minor.
security assessment none0 exceptions over 1,508,597 generated strings; no output longer than its input; ordinary term normalization, outside Lucene's SECURITY.md scope.
registry re-run and audit keepThe finder's test re-run on main with new seeds (3 of 3 fail, -ea and -da); with the fix 3 of 3 pass.
brief runs reproducedThe brief's test through gradle and the standalone runner on main and 10.5.2; fix A and option B validated; after the fact checks, the release probe on the 8 releases 8.8.2-8.11.3 (3 of 3 fail on each).
fact checks of this doc reproduced; corrections appliedAn independent fact check re-ran the doc's gradle command (3 of 3 fail, assertions on and off), fix A with the 5 Bengali suites (25 tests pass), the standalone runner on main and 10.5.2 and the options probe. A second one, by another model family (OpenAI GPT-5.6, read-only), re-read the sources and logs. A diagram check re-ran the diagram's probe. All found the bug as described; their wording and precision corrections are applied.

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,287 words

Finding record

LFV-667, snapshot of 2026-11-06

Registry title
BengaliNormalizer's khio rule tests U+09BF (vowel sign I) instead of U+09B7 (SSA), so the common conjunct ক্ষ (KA+HASANTA+SSA) is never folded to KHA / KA+KHA as the rule comment and the cited paper say, and the malformed KA+HASANTA+VOWEL SIGN I is folded instead
Area
analysis-common / Bengali normalizer (BengaliNormalizer.normalize; BengaliNormalizationFilter, BengaliAnalyzer and the 'bengaliNormalization' factory)
Priority
Low, rank 296: A relevance-only normalization typo (impact class minor) in one language analyzer, whose fix changes indexed terms.
Impact class
minor
Impact note
Relevance quality only, with no error: every application that analyzes Bengali text with BengaliAnalyzer (default constructor included), BengaliNormalizationFilter or the 'bengaliNormalization' factory, on every release from 7.1.0 through 10.5.2 and on main, indexes and queries every word that contains the very common conjunct ক্ষ (শিক্ষা, পরীক্ষা, ক্ষমা, রক্ষা) with a term the documented khio rule would not produce, so the ক্ষ spelling and the খ spelling of a word do not match each other and বক্ষ ('chest') gets the same term as বক্স ('box'), while identical spellings still match; no scale is needed, -ea and -da behave the same, the filter javadoc already warns that normalization may conflate terms, and because a fix changes indexed terms, affected fields need reindexing after it.
Summary
You analyze Bengali text with the stock BengaliAnalyzer or BengaliNormalizationFilter. Its normalizer has a rule labelled 'Khio (Ka + Hoshonto + Murdorno Sh)' and says it implements a cited paper, which folds the conjunct ক্ষ (KA+HASANTA+SSA) to খ (KHA) at the start of a word and to কখ (KA+KHA) elsewhere, so ক্ষমা should normalize to খমা. Instead the rule's condition compares the third character with U+09BF (BENGALI VOWEL SIGN I) instead of U+09B7 (BENGALI LETTER SSA), one hex digit off, so the rule never fires on real text: ক্ষমা becomes ক্সমা and পরীক্ষা becomes পরিক্সা (only the separate 'All sh' rule applies, SSA to SA), while the malformed sequence KA+HASANTA+VOWEL SIGN I is folded instead (ক্িমা becomes খমা), with no error.
Introduced
1bca06b8a938bfe89b288877399942d217c8453d (the contributor's first commit of apache/lucene-solr PR #238, LUCENE-7940 'Bengali Analyzer for Lucene', now apache/lucene#8989; its message says 'LUCENE-7490', a typo for LUCENE-7940) [read: git blame -w, git log -L and git log -G 09BF on the file in the history clone]: the rule comment '// Khio (Ka + Hoshonto + Murdorno Sh)' and the condition s[i+2] == '\u09BF' arrive together. It landed on master (8.0 line) through the merge 08128f712fc and on branch_7x through the squashed backport e18532cb687 (both by rmuir, 2017-09-05). Not a regression: the only later commits on these lines (8ef6a0da568 LUCENE-9570 reformat, f99deb58b74 #13661 spotless) are formatting. (2017-08-24 (authored; landed on master and branch_7x 2017-09-05; first released in 7.1.0))
First affected version
7.1.0 [exec] (the first release with the org.apache.lucene.analysis.bn package; the history probe fails 3 of 3 there and passes with the one-character fix shadowed). 7.0.1, the last release before it, has no Bengali analysis API [exec: the probe does not compile], so no release ever shipped a working khio rule.
Affected versions
Every release that ships the Bengali analysis API, 7.1.0 through 10.5.2, and main a9779ef3c193; not fixed. [exec] a version-independent public-API probe (BengaliNormalizationFilter over KeywordTokenizer and BengaliAnalyzer.normalize, 3 khio checks) fails on 7.1.0, 7.7.3, 8.0.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 and main (-ea and -da), and passes on 7.1.0 and main with the fix shadowed; the finder's JUnit fails 3 of 3 on 9.12.3, 10.5.2 and main (-ea and -da) (it needs org.apache.lucene.tests.analysis, so it does not compile on 9.0.0). [read] all 63 release tags in the history clone that have the file (7.1.0-7.7.3, 8.0.0-8.8.1, 9.0.0-10.5.2) contain the same condition; upstream main a95c754ee00 (2026-10-26), branch_10x and branch_10_5 still test '\u09BF' (read 2026-10-27).
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: exhaustive check
Upstream and prior reports
none found (searched 2026-10-27, read-only; the prior-report record, verdict new): a scripted prior-report search (40 GitHub issue/PR queries on apache/lucene, 3 commit queries, 2 downstream-repository queries, 32 mailing-list queries over the dev, java-user, issues and commits lists, 4 JIRA queries, 8 web queries; 106 distinct hits, 0 high), the solr-user list, manual GitHub searches (apache/lucene 'Bengali' with all 12 results read, apache/lucene-solr, apache/solr, Elasticsearch, OpenSearch, all of GitHub), the changed files of all 409 open apache/lucene PRs (none under analysis/bn/), the introducing issue gh#8989 (LUCENE-7940, 30 comments) and lucene-solr PR #238 with its review comments (no khio discussion), and the file's full git history. No issue, PR, JIRA ticket, list thread, commit or web page reports the wrong code point or that ক্ষ is never folded. Near-miss, not a report: another contributor's wiki page of Bengali analysis notes (mediawiki.org, 2022, linked from a comment in gh#14659) transcribes the coded rule as 'ক্ি (ka + virama + i)' under the label 'Khio (Ka + Hoshonto + Murdorno Sh)' without noting that the two disagree, and recommends disabling the filter as too aggressive. Related, not the same bug: gh#14659 and PR #14699 (Telugu IndicNormalizer, open), where a maintainer defends the Indic folds as deliberately aggressive. Gaps: no code search for the literal code point; 4 of about 35 copies and ports were opened (all carry the same condition).
Lucene commit tested
main a9779ef3c193 (commit of 2026-10-26; tested 2026-10-27) through gradle in a scratch worktree and through the standalone JUnit runner, with assertions on and off; the 10.5.2 release jars (the brief's test); a version-independent probe on the release jars 7.1.0, 7.7.3, 8.0.0, 8.8.2, 8.9.0, 8.10.0, 8.10.1, 8.11.0, 8.11.1, 8.11.2, 8.11.3, 8.11.4, 9.0.0, 9.12.3, 10.0.0 and 10.5.2 (7.0.1 has no Bengali API). Fix A was validated in the scratch worktree (the new test, the 5 Bengali suites with assertions on and off, checkGoogleJavaFormat, the whole module's 1,912 tests); option B with the 4 upstream Bengali suites.

Lucene and upstream sources

Finding record
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizer.java:21-28 (at a9779ef3c193) (class javadoc: 'Implements the Bengali-language specific algorithm specified in: A Double Metaphone encoding for Bangla and its application in spelling checker'; package-private class), :48 (DirghoI kar writes '\u09BF'), :56 (comment '// Khio (Ka + Hoshonto + Murdorno Sh)'), :57-68 (case '\u0995': condition s[i + 2] == '\u09BF' at :58; i == 0 -> KHA, else -> KA KHA), :125-129 ('All sh': SHA and SSA -> SA)
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizationFilter.java:29-31 (at a9779ef3c193) (javadoc: 'the normalization may cause unrelated terms to conflate'); BengaliNormalizationFilterFactory.java:40, :55-61 (SPI name 'bengaliNormalization'); BengaliAnalyzer.java:104, :119-130, :134-138 (default constructor, createComponents, normalize)
  • GitHub, read-only (2026-10-27): apache/lucene #8989 (LUCENE-7940 comments), apache/lucene-solr PR #238 (conversation and review comments), apache/lucene #14659 and PR #14699, apache/lucene main a95c754ee00 BengaliNormalizer.java (condition unchanged)
  • mediawiki.org 'User:TJones (WMF)/Notes/Unpacking Notes/Bengali' (raw page, read 2026-10-27: the khio label followed by 'ক্ি (ka + virama + i)', no remark on the mismatch)
The finding in 30 seconds
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizer.java:21-28 (at a9779ef3c193) (class javadoc, package-private), :36-39, :46-49, :56-68, :125-129
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizationFilter.java:26-53; (at a9779ef3c193) BengaliAnalyzer.java:119-140; BengaliNormalizationFilterFactory.java:40, :54-62
Who is affected
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliAnalyzer.java:103-106, (at a9779ef3c193) :119-140 (:123-125 keyword marker); BengaliNormalizationFilter.java:29-31, :48-49; BengaliNormalizationFilterFactory.java:40
Why it looks unintended
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizer.java:24-26, (at a9779ef3c193) :48, :56-68, :142-144; BengaliNormalizationFilter.java:27-31
  • lucene/analysis/common/src/test/org/apache/lucene/analysis/bn/TestBengaliNormalizer.java:30-69 (at a9779ef3c193)
  • lucene/MIGRATE.md:837, (at a9779ef3c193) :871-873; lucene/CHANGES.txt:3048, :7861
Suggested fix
  • lucene/MIGRATE.md:871-873 (at a9779ef3c193)
History
  • lucene/CHANGES.txt:7861 (at a9779ef3c193)

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

Repro files

About the diagrams

A light, step-by-step picture of the repro (the words ক্ষমা and খমা), made in parallel with this doc from the executed runs on main a9779ef3c193 and the code.

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.