All findings · Lucene finding · LFV-677 · telugu-analyzer-stopwords-not-normalized

TeluguAnalyzer never removes 13 of its 43 single-word default stop words, such as కూడా ("also"), because its stop list is not normalized

The stock TeluguAnalyzer shortens long vowel signs before its stop filter, but its stop list holds only the long spellings, so 13 of its 43 single-word stop words, such as కూడా ("also"), are indexed as ordinary terms. No error; 8.10.0 to 10.6.0 (10.6.0 by reading) and main.

Summary

Priority
Low, rank 442The stock TeluguAnalyzer keeps 13 default stop words, which costs only extra terms, scoring noise and slightly stricter AND and phrase queries, the same minor quality class as its Bengali twin LFV-672, so both go in Low with this new one first, right after LFV-583.
Impact
minor
Since
8.10.0 (LUCENE-10083, 2021)introduced 2021-09-02 · about 5 years
Affected
8.10.0 to 10.6.0 (10.6.0 by reading) and main
Status
documented
Found by
exhaustive check of every default stop word against the analyzer's own normalization

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

TeluguAnalyzer runs IndicNormalizationFilter, then TeluguNormalizationFilter, then its StopFilter. The normalizer shortens the long vowel signs ీ ూ ే ో to ి ు ె ొ, but the default list holds only the long spellings and the stop filter is an exact lookup, so in the stock analyzer 13 entries can never match a token.

Summary card titled: Lucene TeluguAnalyzer: 13 of its 43 single-word default stop words are never removed. Subtitle: its StopFilter runs after TeluguNormalizationFilter, which folds four long vowel signs, but the default stop list is not normalized: కూడా ('also') is indexed as కుడ. No error. Panel 1, createComponents on కూడా (lines 115-123): StandardTokenizer, DecimalDigitFilter and IndicNormalizationFilter leave it unchanged; TeluguNormalizationFilter turns the vowel sign UU into U (TeluguNormalizer.java:80-81), giving కుడా; StopFilter (line 122) keeps it because the set holds కూడా (stopwords.txt line 21), not కుడా; TeluguStemFilter drops the final vowel sign, giving కుడ. Actual: the indexed term కుడ; expected: no token. Panel 2, 13 of the 43 single-word entries are kept (executed): each has one of the four long vowel signs that TeluguNormalizer folds (lines 76-88: 0C40 to 0C3F, 0C42 to 0C41, 0C47 to 0C46, 0C4B to 0C4A), with a table of the 13 entries, their line numbers and indexed terms. Panel 3, effect and fix: a query for కుడి ('right') also finds a document that has కూడా but not కుడి; a query of only కూడా matches documents (executed on a 5-document index); fix: append the 13 normalized forms, as hi/stopwords.txt does: 0 of 43 kept, upstream Telugu tests pass (16 of 16); a fix changes indexed terms, so fields need reindexing, and a Telugu speaker should review the 13 forms. Footer: executed on main a9779ef3c193 (OpenJDK 25.0.3, Linux aarch64, assertions on and off); the same failure on all 40 releases from 8.10.0 to 10.5.2; not counted: 2 two-word entries, 5 duplicate lines.
The word కూడా ("also") through TeluguAnalyzer: the normalizer shortens ూ to ు before the stop filter, so the filter looks up కుడా, misses the list entry కూడా, and the stemmer indexes కుడ; on the right, the 13 kept entries, the effect and the fix. Select the still to open it at full size. Generated with AI, checked against the code.
  1. You index Telugu text with the stock new TeluguAnalyzer(), for example "రాముడు కూడా వచ్చాడు" (roughly "Ramudu also came") and "రాముడు వచ్చాడు" ("Ramudu came"). కూడా ("also") is in the analyzer's default stop list.
  2. You expect కూడా to be dropped: analyzing it gives no token, and the AND query "రాముడు కూడా" matches both documents, like "రాముడు" alone.
  3. Before the stop filter, the Telugu normalizer shortens the long vowel sign ూ to ు, so the filter looks up కుడా. The list holds only కూడా, nothing matches, and the stemmer indexes the term కుడ.
  4. The AND query becomes +f:రాము +f:కుడ and matches 1 of the 2 documents. 13 of the 43 single-word default stop words survive the same way, among them అయితే, మళ్ళీ, మాత్రమే and ఇప్పటికే; గురించి, which has no long vowel sign, is removed.
  5. No error, with assertions on or off. Hindi's analyzer also runs its stop filter after its normalizers, but its list holds the normalized spellings too, so it removes all of its stop words.

Who is affected

Every application that analyzes Telugu with the stock new TeluguAnalyzer(), at index and query time, since 8.10.0. 13 default stop words become ordinary terms: extra postings and scoring noise, stricter AND and phrase queries, and stem collisions (కూడా and కుడి, "right", both become కుడ). Plain OR queries still find their documents; a fix needs a reindex.

Mechanism, terms and numbers584 words

TeluguAnalyzer normalizes every token before its StopFilter: IndicNormalizationFilter, then TeluguNormalizationFilter, then StopFilter, then TeluguStemFilter. The Telugu normalizer shortens the four long vowel signs ీ ూ ే ో to ి ు ె ొ, but the default stop list te/stopwords.txt holds only the original spellings, and StopFilter is an exact set lookup. So the filter sees కుడా while the list holds కూడా: 13 of the 43 single-word entries (30%), among them కూడా ("also"), అయితే, మళ్ళీ, మాత్రమే and ఇప్పటికే, are never removed and are indexed and searched as ordinary terms, with no error. HindiAnalyzer has the same chain shape, and its list ships the normalized forms, so it removes all of its single-word entries. The list has been like this since the analyzer was added (LUCENE-10083, 8.10.0); every release from 8.10.0 to 10.6.0 (10.6.0 by reading) and main is affected. The fix is a 16-line data change in the Hindi style; it changes indexed terms, so affected fields need reindexing.

Terms

  • Stop words: common words an analyzer drops before indexing and querying. TeluguAnalyzer() "Builds an analyzer with the default stop words" from te/stopwords.txt (TeluguAnalyzer.java:98-101, :42-43).
  • Vowel sign (matra): the mark a Telugu consonant carries for its vowel. ూ (U+0C42) is the long u sign, ు (U+0C41) the short one; కూడా is క + ూ + డ + ా.
  • The chain (TeluguAnalyzer.java:114-125): StandardTokenizer -> DecimalDigitFilter -> SetKeywordMarkerFilter (only with a stem-exclusion set) -> IndicNormalizationFilter (:120) -> TeluguNormalizationFilter (:121) -> StopFilter(result, stopwords) (:122) -> TeluguStemFilter (:123).

The bug

  1. TeluguNormalizer folds the long vowel signs to the short ones on every token that is not marked as a keyword: ీ -> ి, ూ -> ు, ే -> ె, ో -> ొ (TeluguNormalizer.java:76-88, "// long -> short vowels matras"). The default constructor adds no keyword marker.
  2. StopFilter.accept() is an exact CharArraySet lookup of the current term (StopFilter.java:96-97).
  3. te/stopwords.txt has only the header # Telugu stop word list and the original spellings. Every entry with one of those four vowel signs is therefore unreachable in the stock analyzer (no stem-exclusion set): the filter sees కుడా, the set holds కూడా. The word goes on to the stemmer and is indexed as కుడ.

The numbers

The file has 50 entry lines, 45 distinct entries (5 duplicate lines), 2 of them two words (lines 31 and 45, which can never match one token). Of the 43 single-word entries, 13 (30%) are never removed: మెచ్చుకో, చేయగలిగింది, ఇప్పటికే, మళ్ళీ, అయితే, కూడా, అందుబాటులో, వేరుగా, మాత్రమే, అందరూ, దూరంగా, చుట్టూ, వ్యతిరేకంగా (lines 2, 11, 12, 14, 18, 20, 21, 22, 37, 39, 43, 50, 51). Each differs from its normalized form only in those vowel signs. The entry గురించి has none of them and is removed.

Not affected

  • Words named in a stem-exclusion set: the keyword marker makes TeluguNormalizationFilter skip them, so the raw entry matches (IndicNormalizationFilter ignores the marker, but it changes none of the 13 entries).
  • normalize() (TeluguAnalyzer.java:127-133, used for prefix, wildcard and fuzzy terms): it has no StopFilter.
  • Custom stop sets that hold the normalized forms.

A sweep ran every default entry of the 39 stock analysis-common analyzers that have a default stop set through the whole analyzer. Read entry by entry, the leftovers outside Telugu and BengaliAnalyzer are comment lines, hyphenated, elided or multi-word entries, so only these two lose single-word entries to pre-stop normalization (13 each; the Bengali case is a separate, already publicly described bug).

Exposure and likelihood338 words
  • Who is hit. The default constructor of a stock language analyzer, so every Telugu field that uses it, at index and query time, on every release from 8.10.0. Also code that reuses TeluguAnalyzer.getDefaultStopSet() after the same two normalization filters. Only Telugu fields use this analyzer. No dedicated Elasticsearch or OpenSearch Telugu analyzer was found (the Elasticsearch request #84800 is open), but Solr can configure a Lucene analyzer class by name (<analyzer class="...">, built with its no-arg constructor), so Solr users can use it directly.
  • What goes wrong (5-document probe index, stock analyzer vs the fixed list, assertions on and off):
    • the 13 words are indexed: extra postings, scoring noise, and no position gap where a stop word should leave one;
    • a query made only of them still matches: the term query కూడా matches 2 documents, where the fixed analyzer builds no query at all;
    • AND and phrase queries that contain one are stricter: MUST("రాముడు కూడా") matches 1 document instead of 3, the phrase "రాముడు కూడా వచ్చాడు" 1 instead of 2;
    • the stemmer folds the kept word onto an unrelated one: కూడా and కుడి ("right") both give కుడ, so the query కుడి also finds "రాముడు కూడా వచ్చాడు".
  • What does not go wrong. Index and query share the chain, so plain OR queries still find their documents. No exception, no corruption, and no unusual cost observed: 20,000 seeded random Telugu texts gave 0 exceptions, and 200,000 copies of కూడా take the same 4,188 index bytes as 200,000 copies of an ordinary word (one codec, one single-document segment). Deterministic; no scale needed.
  • How common. Every such index gets these terms whenever its text contains one of the 13 words; their corpus frequency was not measured. Impact class: minor relevance quality. Compared with the intended stop-word behaviour it does lose matches for AND and phrase queries that contain one of the words (above), and the stem collision adds false ones; plain OR queries are not affected.
  • Reindex. Any fix changes indexed and query terms, so affected fields need reindexing.

Minimal repro

The test shows that the stock new TeluguAnalyzer() does not remove its own default stop word కూడా ('also'), because its StopFilter runs after normalization and the list is not normalized.

TestTeluguDefaultStopword.java (JUnit test)30 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.te;

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

public class TestTeluguDefaultStopword extends BaseTokenStreamTestCase {

  public void testDefaultStopwordRemoved() throws Exception {
    try (TeluguAnalyzer a = new TeluguAnalyzer()) {
      assertTrue(a.getStopwordSet().contains("కూడా"));
      // expected: no token for the stop word కూడా; actual: the token కుడ (normalized, stemmed)
      assertAnalyzesTo(a, "కూడా", new String[0]);
    }
  }
}
Command
# in a copy of this finding's repro folder (repro/LFV-677/, 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.te.TestTeluguDefaultStopword
Expected vs actual
Expected no token; actual one token: TokenStream has more tokens than expected (expected count=0); extra token=కుడ (-ea and -da).

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

More files, environment, full output45 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
    => java.lang.AssertionError: TokenStream has more tokens than expected (expected count=0); extra token=కుడ
       org.junit.Assert.fail(Assert.java:89)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:402)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:513)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:573)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:637)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertAnalyzesTo(BaseTokenStreamTestCase.java:792)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertAnalyzesTo(BaseTokenStreamTestCase.java:777)
       org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertAnalyzesTo(BaseTokenStreamTestCase.java:862)
       org.apache.lucene.analysis.te.TestTeluguDefaultStopword.testDefaultStopwordRemoved(TestTeluguDefaultStopword.java:27)

Why it looks unintended

  • Hindi's analyzer has the same chain shape, and its list "also contains forms normalized by HindiNormalizer".
  • Arabic filters stop words before normalizing because "the stopword list is not normalized!"; Persian after, because its list "is normalized with the above!"
  • No test, review or changelog records a choice; the Telugu tests never check that a stop word is removed.
The full argument365 words
  • Contract (implied, not literal). TeluguAnalyzer() "Builds an analyzer with the default stop words" (:98), the field is the "File containing default Telugu stopwords." (:42-43), and the createComponents javadoc lists "Telugu Stop words" in the chain. No sentence literally promises that every entry is removed, but an entry no input can reach through the default analyzer (no stem-exclusion set) is dead data in a shipped default resource.
  • Lucene's other stop files and analyzers state the same rule for their own lists (a convention stated file by file, not a general API contract): a list must match the token form where its StopFilter runs: hi/stopwords.txt:4-7 ("by default this file also contains forms normalized by HindiNormalizer for spelling variation ... such that it can be used whether or not you enable that feature. When adding additional entries to this list, please add the normalized form as well."); ArabicAnalyzer.java:135 ("the order here is important: the stopword list is not normalized!", so its StopFilter runs before normalization) and ar/stopwords.txt:4; PersianAnalyzer.java:136-137 and fa/stopwords.txt:4-5 ("used after normalization"); ckb/stopwords.txt:2 ("normalized with our scheme"); el/stopwords.txt:2-3.
  • A copied template. The Telugu createComponents javadoc is HindiAnalyzer's with Hindi replaced by Telugu and LowerCaseFilter left out, and the chain has the same shape (HindiAnalyzer.java:122-129; Hindi also lowercases first); the Hindi list rule was not copied.
  • No recorded choice. te/stopwords.txt has exactly one commit (the one that added the analyzer). The list, the four folds and the filter order were all in PR #275's first commit; the PR has one review ("LGTM") and no line comments; #11121, CHANGES and MIGRATE say nothing about Telugu stop words. TestTeluguAnalyzer never checks that a stop word is removed.
  • Precedent. LUCENE-1966 (#3041, 2009) caught the same mismatch for ArabicAnalyzer in review and settled it by running StopFilter first. Stop-list data fixes have gone under Bug Fixes (GITHUB#14075) and Improvements (GITHUB#12172, GITHUB#16718), so the CHANGES section is the committers' call.
  • The createComponents javadoc lists the stop words after TeluguStemFilter while the code runs StopFilter before it, and SetKeywordMarkerFilter after both normalizers while the code adds it before them (HindiAnalyzer has the same two doc nits); the 13 entries are dead in either order.
The devil's-advocate check205 words

The intended-behaviour devil's advocate and the cross-model reviewer's intended lens built the strongest case for "not a bug": no javadoc promises that every entry is removed; StopFilter is exact matching and never promises to normalize a supplied set; the list looks like rough data (the devil's advocate's non-native reading: a word-by-word translation of the a-words of an English stop list, which would explain the duplicate pairs, the two-word entries and content words such as "available"); the fold is lossy, Telugu normalization has been questioned upstream (#14659, about IndicNormalizer's separate వు -> మ rule, not these four folds), and upstream might prefer to change the fold; and with a stem-exclusion set the raw entries do match.

Why it fails: Lucene's other stop files and analyzers state the rule for their own lists, and TeluguAnalyzer follows neither form of it; a rough list explains how the error happened, but nobody chose to ship entries that can never fire in the default analyzer; "change the fold" is a choice of fix, not intent (the fold is documented: "long -> short vowels matras"); the keyword path needs a user-supplied exclusion set naming the stop word, which the default analyzer never has. Every checker's verdict: real; the adjudication's class: minor.

Suggested fix

Recommended Add the 13 normalized forms to te/stopwords.txt (the Hindi style)

Add the 13 normalized forms to te/stopwords.txt, as Hindi's list does (16 lines, no code change). The repro, the Telugu tests and all of lucene/analysis/common pass. Indexed terms change, so affected fields need reindexing, and a Telugu speaker should review the added forms.

All options and trade-offs4 options · 853 words

Two fixes were applied in a scratch worktree at a9779ef3c193 and run through gradle (seed DEADBEEF); the stop-filter-first option and the workaround were not run.

Add the 13 normalized forms to te/stopwords.txt (the Hindi style) (validated by the checks) (recommended)

Append a 3-line comment and the 13 forms that the analyzer's own pre-stop filters produce for the 13 entries. The raw spellings stay, so the list still works in a chain that filters stop words before normalizing. Add the repro (or its first two tests) to the Telugu tests as the regression test; it states a broader rule than any javadoc does, so a test focused on the 13 forms is a less brittle choice if a committer prefers.

--- a/lucene/analysis/common/src/resources/org/apache/lucene/analysis/te/stopwords.txt
+++ b/lucene/analysis/common/src/resources/org/apache/lucene/analysis/te/stopwords.txt
@@ -49,3 +49,19 @@
 వద్ద
 అందుబాటులో
 దూరంగా
+# TeluguAnalyzer applies this list after IndicNormalizationFilter and TeluguNormalizationFilter,
+# which shorten long vowel signs. The forms below are the normalized forms of the entries above
+# that those filters change. When adding entries, please add the normalized form as well.
+చెయగలిగింది
+మళ్ళి
+వ్యతిరెకంగా
+అందరు
+మాత్రమె
+ఇప్పటికె
+కుడా
+అయితె
+వెరుగా
+మెచ్చుకొ
+చుట్టు
+అందుబాటులొ
+దురంగా

Trade-offs

  • Reindex. Indexed and query terms change for text with the 13 words (old documents keep కుడ, new queries drop the word): needs a CHANGES entry and a MIGRATE note.
  • Over-stopping: ask a Telugu speaker to review the 13 forms. The fold can merge distinct words, so a normalized form may also be an ordinary word: చుట్టు, the normal form of చుట్టూ ("around"), is also an ordinary verb form. The Hindi list accepts the same cost; a reviewer may drop individual forms.
  • getDefaultStopSet() grows from 45 to 58 entries; custom chains that reuse it after the two normalizers are fixed too. No API change.
  • The list stays consistent only by convention; the comment asks future editors to add normalized forms.

What the checks validated

  • Stock main a9779ef3c193 (gradle, seed DEADBEEF): the repro fails 3 of 4 with assertions on and off; the four upstream Telugu test classes pass (16 tests).
  • With the fix: the repro and the four Telugu classes pass (20 tests, 0 failures) with assertions on, and again with assertions off; the repro alone passes with assertions off (4 tests); all of :lucene:analysis:common:test with the repro added passes (1,913 tests, 0 failures, 18 skipped); ecjLintTest and checkGoogleJavaFormat pass with the final test file.
  • Run order, for the record: the stock gradle runs and the module-wide run used the test before checkGoogleJavaFormat asked for a different line wrapping; the final file differs only in layout, and the fix run with it was repeated.
  • Through the classpath runner: the repro passes 4 of 4 with -ea and -da with the fixed list first on the classpath; the default set then has 58 entries (45 before).
  • fix.diff and fix-with-test.diff (the fix plus the repro as a new test file) pass git apply --check on a clean tree.

Normalize the default set when it is loaded (validated by the checks)

In TeluguAnalyzer.DefaultSetHolder, add normalize("", entry) of every entry (the same DecimalDigitFilter, IndicNormalizationFilter and TeluguNormalizationFilter the analyzer runs before its StopFilter) to the loaded set and keep it unmodifiable (+10/-1 lines in TeluguAnalyzer.java).

CharArraySet raw = WordlistLoader.getWordSet(/* te/stopwords.txt, as before */);
// The StopFilter runs after IndicNormalizationFilter and TeluguNormalizationFilter, so also
// add every entry in the form those filters produce (as normalize() does).
CharArraySet set = new CharArraySet(raw, false);
try (TeluguAnalyzer normalizer = new TeluguAnalyzer(CharArraySet.EMPTY_SET)) {
  for (Object entry : raw) {
    set.add(normalizer.normalize("", new String((char[]) entry)).utf8ToString());
  }
}
DEFAULT_STOP_SET = CharArraySet.unmodifiableSet(set);

Trade-offs

Future list edits stay consistent automatically and the data file is unchanged; but it is code in a static initializer that builds a temporary TeluguAnalyzer, Analyzer.normalize treats each entry as one token (no tokenizer), so it also adds the normalized form of a two-word entry (harmless for the stock chain; 59 entries), and the same reindex and over-stopping review apply.

What the checks validated

  • With the change (gradle, seed DEADBEEF, assertions on): the repro and the four Telugu classes pass (20 tests, 0 failures); ecjLintMain and checkGoogleJavaFormat pass.
  • Through the classpath runner (the changed class shadowed onto the main jars): the repro passes 4 of 4 with -ea and -da; the default set then has 59 entries.
  • Not run: the whole module with this change.

Run the StopFilter before the normalizers (the Arabic order)

Move StopFilter above IndicNormalizationFilter in createComponents.

Trade-offs

Not run. The 13 raw spellings would then match, but the analyzer would stop removing variant spellings that the normalizer folds onto an entry today (for example a word written with a long vowel sign where the list has the short one), and users who worked around the bug with a normalized custom stop set would break. Not recommended.

Workaround for users today (not a fix)

Pass a stop set that also holds the normalized forms: new TeluguAnalyzer(set) with the default set plus analyzer.normalize("", w) of each entry. The independent reproducer's control shows that a set holding కుడా removes కూడా.

Trade-offs

Changes indexed terms, so it also needs reindexing, and it does not help other users of the default list.

How it was found

An exhaustive check on the real jar: for every default stop word of the analyzers that run their stop filter after these normalizers, is the analyzer's own normalized form still in the stop set? It flagged 14 of the 45 Telugu entries (13 single-word) and none for Hindi or Persian; the finding was then reduced to a public-API JUnit.

The method in full153 words

Method: bounded-exhaustive checking of the stock jar against the analyzer's own documented chain (semi-formal tier), in a hunt unit that checked script normalizers against their documented rules.

  • For every entry of the default stop set of each analyzer that runs StopFilter after these normalizers, the check ran the entry through exactly the analyzer's pre-stop filters and asked whether the result is still in the set. Telugu: 14 of 45 entries flagged (the 13 single-word entries plus one two-word entry); Bengali 13; Hindi and Persian 0.
  • It was confirmed end to end with Analyzer.tokenStream, then reduced to a 2-test public-API JUnit that fails on the stock main jars with assertions on and off.
  • Checked by an independent reproducer, a reviewer from another model family (OpenAI GPT-5.6, refute and intended lenses, both executed), an intended-behaviour devil's advocate, an adjudication, a history, prior-report and security follow-up, and an audit. Found 2026-10-27 on main a9779ef3c193.

Prior reports, history and checks

Prior reports

No prior report found (2026-10-27): 177 logged queries over GitHub issues, PRs and commits, the Lucene and Solr lists, the JIRA archive, downstream trackers and the web, plus the changed files of all 409 open PRs. Another contributor's public note describes the same mismatch for the Bengali analyzer only.

Searches3 searches

Verdict new for Telugu: no report by anyone; the same mismatch pattern is publicly described for the Bengali analyzer only (a separate bug). Every upstream Telugu item was read in full and none mentions stop words. Keyword search cannot prove absence; a report written only in Telugu script could be missed (two Telugu-script queries found nothing), and private reports are not searchable.

What was searched, when, and what turned up
WhereWhenHowFound
GitHub issues, PRs and commits (apache/lucene), downstream repos, dev@, java-user@, issues@, commits@, the JIRA archive, the web2026-10-27the hunt's prior-report tool: 65 queries (24 GitHub, 3 commit, 2 downstream, 24 list, 6 JIRA, 6 web), 136 distinct hits; a defect-class run (38 queries, 437 hits); the finder's run (24 queries)no report; nearest: #11121 / PR #275 (added the analyzer), #13059 (normalize() wiring), #14659, #14699, #14657 (IndicNormalizer's Telugu vu -> ma rule)
apache/lucene and the OpenSearch, Elasticsearch and Solr repos; all 409 open apache/lucene PRs; commits API on main and branch_10x2026-10-2726 issue searches (two in Telugu script), 3 code searches, changed-file scan of every open PRno report; no open PR touches te/stopwords.txt (#14699 touches IndicNormalizer and Telugu tests only)
JIRA (LUCENE, SOLR), 7 mailing lists incl. the Solr lists, wiki notes of a search team's analysis work, the web, the Solr Reference Guide2026-10-274 JIRA and 21 list queries, 10 wiki searches and 8 page reads, 6 web queriessame defect class elsewhere only: LUCENE-1966 (#3041, ArabicAnalyzer, 2009, settled by design), LUCENE-1963 (#3038), #12172; another contributor's 2022 wiki note on BengaliAnalyzer's list (not Telugu)

History

Born with the analyzer: 544dbbea468 (LUCENE-10083, PR #275, 2021-09-02), first released in 8.10.0. The list has not changed since; the finder's JUnit fails on all 40 releases from 8.10.0 to 10.5.2 and on main.

Versions tested6 rows

Introduced by 544dbbea468 "LUCENE-10083: Analyzer and stemmer for Telugu language" (apache/lucene PR #275 by @vinodrenu, approved and merged by @rmuir on 2021-09-02; issue #11121), backported to branch_8x of the former lucene-solr repository as 114c9dff46c the same day. Not a regression: the list, the filter order and the four vowel-sign folds were already in the PR's first commit c143ef618e (2021-08-13).

te/stopwords.txt has no later commit and is the same blob at every 9.x and 10.x tag, in the 8.10.0 and 8.11.4 jars, on main, on the unreleased branch_10x head and on the head of branch_10_6, from which 10.6.0 was released on 2026-10-29. The later commits to TeluguAnalyzer.java (LUCENE-10335 stop-file loading, #13059 normalize() only) and to TeluguNormalizer.java (visibility, formatting, a comment typo) do not change this. The te classes say @since 9.0.0, but they first shipped in 8.10.0.

Versions and branches executed, with results
Version or commitVerdictResult
main a9779ef3c193 affectedthis doc's JUnit fails 3 of 4 through gradle (assertions on and off) and the classpath runner (-ea, -da); passes with either fix
10.0.0 to 10.5.2 (12 releases) affectedthe finder's JUnit fails 2 of 2, the same 13 words; this doc's JUnit fails 3 of 4 on 10.5.2
9.0.0 to 9.12.3 (21 releases) affectedthe finder's JUnit fails 2 of 2 (9.0.0 with the old test-framework import); this doc's JUnit fails 3 of 4 on 9.12.3
8.10.0, 8.10.1, 8.11.0 to 8.11.4 first release 8.10.0the finder's JUnit (old import) fails 2 of 2; 8.10.0 with only the fixed list first on the classpath passes
544dbbea468 (its te sources over the 9.0.0 jars) introducing commitfails 2 of 2, the same message (a consistency check, not a full build of that commit)
8.9.0 API absentno TeluguAnalyzer (compile error)

Independent checks

An independent reproducer, a reviewer from another model family (OpenAI GPT-5.6, two executed lenses), an intended-behaviour devil's advocate, an adjudication and an audit each re-ran it with their own tests or new seeds; all say real, every control passes, and this doc's gradle runs reproduce it on a9779ef3c193.

Checks and verdicts346 words

Every check executed code on the stock main jars; the cross-model reviewer ran on main ea57360912ed, whose lucene/analysis sources equal a9779ef3c193.

Each independent check and its verdict
CheckVerdictWhat it found
finder candidateStop-list model over six analyzers (Telugu 14 of 45 flagged, Hindi and Persian 0), then the 2-test JUnit: fails 2 of 2 on main (-ea, -da), 10.5.2 and 9.12.3; passes with the fixed list; upstream Telugu tests 16 of 16 with it.
independent reproducer realOwn 5-test JUnit written from the claim: fails 2 of 5 on main (-ea, -da), 10.5.2, 9.12.3, 8.11.4 and 8.10.0, 3 controls pass; its sweep of 39 analyzers, read entry by entry, leaves single-word defaults lost to pre-stop normalization only in Telugu and Bengali.
cross-model review, refute lens (OpenAI GPT-5.6, executed) real, highRefutation failed: own pipeline test fails on main (also -da and another seed), 10.5.2 and 9.12.3; the fixed list passes; upstream Telugu tests 16 of 16 with it.
cross-model review, intended lens (OpenAI GPT-5.6, executed) real, high (intended argument moderate)Strongest case: exact-match StopFilter, lossy fold; rejected because the Arabic, Persian, Hindi and Sorani analyzers align list and filter position. Its survey: those four remove every single-word default.
intended-behaviour devil's advocate real (high on facts, moderate on how upstream files it)Re-ran the finder's JUnit on main (-ea, -da), 10.5.2, 9.12.3 and 8.10.0; its probe shows the 13 stay dead in the javadoc's order too, the keyword path, and Hindi with 0 dead entries.
adjudication confirmed, high; class minorRe-ran the independent reproduction's JUnit (2 of 5 fail, -ea), the finder's (2 of 2 fail, -da) and the finder's with the fixed list (2 of 2 pass), seeds 1DEA1234/1DEA1235.
audit keepRe-ran the finder's JUnit on a clean main checkout with new seeds (-ea, -da): 2 of 2 fail each.
this doc's runs reproduced; fix validatedGradle in a clean worktree: the repro fails 3 of 4 on stock (assertions on and off) and passes with either fix; lucene/analysis/common passes with the recommended fix.

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

Finding record

LFV-677, snapshot of 2026-11-06

Registry title
TeluguAnalyzer runs its StopFilter after Indic and Telugu normalization, but its default stop list is not normalized, so 13 of the 43 single-word default stop words (for example కూడా, 'also') are never removed
Area
analysis-common / language analyzers (TeluguAnalyzer default stop set te/stopwords.txt, applied after IndicNormalizationFilter and TeluguNormalizationFilter)
Priority
Low, rank 442: The stock TeluguAnalyzer keeps 13 default stop words, which costs only extra terms, scoring noise and slightly stricter AND and phrase queries, the same minor quality class as its Bengali twin LFV-672, so both go in Low with this new one first, right after LFV-583.
Impact class
minor
Impact note
Relevance quality and index size only, with no error: every application that analyzes Telugu text with the stock new TeluguAnalyzer() (or reuses TeluguAnalyzer.getDefaultStopSet() after the same two normalization filters), on every release from 8.10.0 through 10.6.0 (10.6.0 by reading) and on main, indexes and queries 13 common function words from the default list (among them కూడా 'also', అయితే, మళ్ళీ, మాత్రమే and ఇప్పటికే) as ordinary terms (కూడా is indexed as కుడ), so they take postings, add scoring noise and leave no position gap, a query made only of them still matches documents, AND and phrase queries that contain one are stricter than with the word removed, and because the stemmer folds a kept word onto unrelated words, a query for కుడి ('right') also matches a document that contains only కూడా (all shown on a 5-document index by the security probe); index and query use the same chain, so plain OR queries still find their documents, no scale is needed, -ea and -da behave the same, and because a fix changes indexed terms, affected fields need reindexing after it.
Summary
You analyze Telugu text with the stock new TeluguAnalyzer(), whose default stop list (te/stopwords.txt) holds common function words such as కూడా ('also') that the analyzer should drop. The analyzer normalizes every token before its StopFilter and folds long vowel signs to short ones (ూ to ు, ీ to ి, ే to ె, ో to ొ), but the list holds only the unnormalized spellings, so the StopFilter sees కుడా and never matches the entry కూడా. As a result 13 of the 43 single-word default stop words are never removed and are indexed and searched as ordinary terms, while the Hindi analyzer, which has the same chain, ships the normalized forms in its list and removes all of its stop words.
Introduced
544dbbea46878b49be5c4c4e82d9b06a5679c891 (LUCENE-10083 'Analyzer and stemmer for Telugu language', apache/lucene PR #275 by vinodrenu, approved ('LGTM') and merged by rmuir on 2021-09-02; issue #11121; backported to branch_8x of the former lucene-solr repository as 114c9dff46c the same day) [read: git log --follow in the history clone, PR #275 and its commits through the read-only GitHub API]. It is the commit that added TeluguAnalyzer, so the bug shipped with the feature and is not a regression: the stop list (blob ea175d67), the StopFilter placed after both normalization filters and the four long-vowel-sign folds were already in the PR's first commit c143ef618e (2021-08-13). te/stopwords.txt has had no commit since. The later TeluguAnalyzer.java commits do not change this behaviour: cc342ea7407 (LUCENE-10335, stop-file loading only), 9caeb9395da (#13059, normalize() only), 694d797526a, f99deb58b74 and 54283150a81 (visibility, formatting, comments). (2021-09-02 (merged on main and backported to branch_8x the same day; first released in 8.10.0))
First affected version
8.10.0 [exec] (the first release with TeluguAnalyzer; CHANGES lists LUCENE-10083 under 8.10.0 New Features). 8.9.0, the last release before it, has no Telugu analysis API [exec: the repro does not compile]. On the main line the first affected release is 9.0.0.
Affected versions
Every release that has TeluguAnalyzer, 8.10.0 through 10.6.0 (10.6.0 by reading), and main a9779ef3c193; not fixed. [exec] the follow-up's history matrix ran the finder's JUnit (2 tests, public API) 48 times: it fails 2 of 2 with the identical message (same 13 survivors) on all 40 releases (8.10.0, 8.10.1, 8.11.0-8.11.4, the 21 releases 9.0.0-9.12.3 and the 12 releases 10.0.0-10.5.2), on the introducing commit's sources over the 9.0.0 jars and on main, with extra -da runs on 8.10.0, 9.12.3, 10.5.2 and main; 8.9.0 does not compile (API absent); 8.10.0 with only the fixed stop list first on the classpath passes. The independent reproduction's own JUnit fails on main (-ea and -da), 10.5.2, 9.12.3, 8.11.4 and 8.10.0; the intended-behaviour check re-ran the finder's JUnit on main (-ea and -da) and on 10.5.2, 9.12.3 and 8.10.0 (-da), all failing. [read] te/stopwords.txt is the same blob (ea175d67) at every 9.x and 10.x tag, inside the 8.10.0 and 8.11.4 jars, on main, on the unreleased branch_10x head and on the head of branch_10_6, from which 10.6.0 was released on 2026-10-29 (read through the GitHub contents API on 2026-10-27, not executed); upstream main a95c754ee003 (2026-10-26) is unchanged.
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): 177 logged queries, among them a scripted prior-report search (65 queries: 24 GitHub, 3 commit, 2 downstream, 24 mailing-list queries over the dev, java-user, issues and commits lists, 6 JIRA, 6 web; 136 distinct hits, none rated high), a defect-class run (38 queries, 437 hits), the finder's earlier run (24 queries) and 74 manual searches (26 GitHub issue searches including two in Telugu script, 3 code searches, a scan of the touched files of all 409 open apache/lucene PRs, commits-API checks on main and branch_10x, 4 JIRA queries over LUCENE and SOLR, 21 queries on 7 lists including the Solr lists, mediawiki.org notes pages, 6 web queries, the Solr Reference Guide). Every upstream Telugu item was read in full and none mentions stop words: #11121 / LUCENE-10083 and PR #275 (introduced the analyzer and the list; one 'LGTM' review), #13059 (normalize() wiring fix, a different defect), #14659, #14699 and #14657 (IndicNormalizer's Telugu vu -> ma rule). Same defect class elsewhere, not this bug: LUCENE-1966 (#3041, 2009, ArabicAnalyzer, settled by running StopFilter before normalization), LUCENE-1963 (#3038) and #12172 (other stop-list fixes), and another contributor's public 2022 wiki note that describes the same mismatch for BengaliAnalyzer only. No open PR touches te/stopwords.txt (#14699 touches IndicNormalizer and Telugu tests; #16911 deletes all of branch_10x). Not fixed: upstream main a95c754ee003 and branch_10x unchanged. Gaps: keyword search only (a report written only in Telugu script could be missed; two Telugu-script queries found nothing); private reports and chat are not searchable.
Lucene commit tested
main a9779ef3c193 (commit of 2026-10-26): this doc's JUnit through gradle in a clean worktree, stock and with the recommended fix with assertions on and off, and with the load-time fix with assertions on, plus the four upstream Telugu test classes and all of lucene/analysis/common with the recommended fix; through the hunt's classpath runner, the same JUnit on main (-ea, -da, both fixes), 10.5.2 and 9.12.3. Hunt runs: the finder's JUnit on all 40 releases from 8.10.0 to 10.5.2, on the introducing commit's sources and on main.

Lucene and upstream sources

Finding record
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/te/TeluguAnalyzer.java:42-43 (at a9779ef3c193) (DEFAULT_STOPWORD_FILE, 'File containing default Telugu stopwords'), :47-54 (getDefaultStopSet), :98-101 (TeluguAnalyzer(): 'Builds an analyzer with the default stop words'), :103-112 (createComponents javadoc, which lists 'Telugu Stop words' after TeluguStemFilter), :114-125 (chain: StandardTokenizer, DecimalDigitFilter, SetKeywordMarkerFilter only with a stem-exclusion set, IndicNormalizationFilter, TeluguNormalizationFilter, StopFilter at :122, TeluguStemFilter), :127-133 (normalize(), no StopFilter)
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/te/TeluguNormalizer.java:76-88 (at a9779ef3c193) ('// long -> short vowels matras': U+0C40 -> U+0C3F, U+0C42 -> U+0C41, U+0C47 -> U+0C46, U+0C4B -> U+0C4A)
  • lucene/analysis/common/src/resources/org/apache/lucene/analysis/te/stopwords.txt:1-51 (at a9779ef3c193) (header only '# Telugu stop word list'; 50 entry lines, 45 distinct, 5 duplicates, multi-word entries at :31 and :45; the 13 dead entries at :2, :11, :12, :14, :18, :20, :21, :22, :37, :39, :43, :50, :51)
  • lucene/analysis/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt:4-7 (at a9779ef3c193) ('by default this file also contains forms normalized by HindiNormalizer ... please add the normalized form as well'); ckb/stopwords.txt:2 ('normalized with our scheme'); lucene/analysis/common/src/java/org/apache/lucene/analysis/ar/ArabicAnalyzer.java:135-136 ('the order here is important: the stopword list is not normalized!', StopFilter before normalization); fa/PersianAnalyzer.java:136 ('the stopword list is normalized with the ...')
  • lucene/analysis/common/src/test/org/apache/lucene/analysis/te/TestTeluguAnalyzer.java (at a9779ef3c193) (no test checks that a default stop word is removed); lucene/CHANGES.txt:5215 ('LUCENE-10083: Analyzer and stemmer for Telugu language' under 8.10.0, section at :5192) and :3048 ('GITHUB#13059: Fixed missing IndicNormalization and DecimalDigit filters in TeluguAnalyzer normalization' under 9.10.0)
  • GitHub, read-only (2026-10-27): apache/lucene PR #275 (commits c143ef618e, 6c0c09933f; review), #11121, #13059, #14659, #14699, #14657, #3041 (LUCENE-1966), #3038 (LUCENE-1963), #12172, #16911; upstream main a95c754ee003 and branch_10x te/stopwords.txt unchanged
The finding in 30 seconds
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/te/TeluguAnalyzer.java:42-43, :94-101, :114-125, :127-133
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/te/TeluguNormalizer.java:76-88
  • lucene/core/src/java/org/apache/lucene/analysis/StopFilter.java:96-97
  • lucene/analysis/common/src/resources/org/apache/lucene/analysis/te/stopwords.txt:1-51 (lines 2, 11, 12, 14, 18, 20, 21, 22, 37, 39, 43, 50, 51; :31, :45 two-word)
Why it looks unintended
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/te/TeluguAnalyzer.java:42-43, :98, :103-112
  • lucene/analysis/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt:4-7; lucene/analysis/common/src/resources/org/apache/lucene/analysis/ar/stopwords.txt:4-6; lucene/analysis/common/src/resources/org/apache/lucene/analysis/fa/stopwords.txt:4-5; lucene/analysis/common/src/resources/org/apache/lucene/analysis/ckb/stopwords.txt:2; lucene/analysis/common/src/resources/org/apache/lucene/analysis/el/stopwords.txt:2-3
  • lucene/analysis/common/src/java/org/apache/lucene/analysis/ar/ArabicAnalyzer.java:135-138; lucene/analysis/common/src/java/org/apache/lucene/analysis/fa/PersianAnalyzer.java:135-139; lucene/analysis/common/src/java/org/apache/lucene/analysis/hi/HindiAnalyzer.java:110-119, :123-129
  • lucene/analysis/common/src/test/org/apache/lucene/analysis/te/TestTeluguAnalyzer.java (six tests, none checks stop-word removal)
History
  • lucene/CHANGES.txt:5192, :5215 (LUCENE-10083 under 8.10.0), :3048 (GITHUB#13059, 9.10.0)

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

Repro files

About the diagrams

A summary card (the PNG), made in parallel with this doc; the alt text describes the PNG as delivered on 2026-10-28.

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.