Lucene picks the decoder in getDecoder: its own ISO-8859-14 table for the exact string ISO8859-14, two exact aliases, then Charset.forName. The JDK has no ISO-8859-10 or ISO-8859-14 charset, so every other spelling of those two throws. C Hunspell lower-cases the name, keeps letters and digits, and has both tables.
- A Hunspell dictionary is two files: an affix file (
.aff) with the rules and a word list (.dic). Many older dictionaries store words in an 8-bit encoding, one byte per letter. - The affix file's
SETline names that encoding, for exampleSET ISO8859-2. The same byte means different letters in different encodings: 0xBF isŋin ISO-8859-10 but¿in Latin-1. - Lucene reads the
SETline first and turns the name into a Java decoder for both files. Without a decoder the dictionary cannot be read at all.
ISO8859-14 reaches Lucene's own table; every other name goes to the JDK, which knows ISO-8859-2 under several spellings but has no ISO-8859-10 or ISO-8859-14 at all. Select the still to open it at full size. Generated with AI, checked against the code.- Write a two-line Hunspell dictionary: the affix file says
SET ISO8859-10(Latin-6, Nordic) and the word list holds one word, the bytesx,0xBF,y. In ISO-8859-10, byte 0xBF is the letterŋ. - Load it with
new Dictionary(dir, "d", aff, dic). You expect it to load andspell("xŋy")to be true: the Hunspell manual lists ISO8859-1 to ISO8859-10, and C Hunspell reads such a file. - Instead the constructor throws
UnsupportedCharsetException: ISO8859-10. Lucene uses its own table only for the exact nameISO8859-14and hands every other name to the JDK, which has no ISO-8859-10 or ISO-8859-14 charset at all. - So
ISO-8859-14andiso8859-14fail too. Of 32 spellings each, ISO8859-10 loads in 0 and ISO8859-14 in 1, while ISO8859-2 and the other listed parts load in 8 to 10. - It fails loudly at load time, so nothing is misread. Matching the name as C Hunspell does (lower case, letters and digits only) plus a 96-entry ISO-8859-10 table makes all 64 spellings load and decode correctly.
Users of the opt-in Hunspell module whose affix file says SET ISO8859-10, or writes ISO-8859-14 other than exactly ISO8859-14. The dictionary never loads, so the stem filter and spell-checker never get one; nothing is misread or stored. No surveyed public dictionary uses these values. Workaround: convert the dictionary to UTF-8.
Mechanism, terms and numbers518 words
A Hunspell affix file names its character encoding on a SET line; the Hunspell manual lists "UTF-8, ISO8859-1 - ISO8859-10, ISO8859-13 - ISO8859-15, KOI8-R, KOI8-U, cp1251, ISCII-DEVANAGARI". Lucene turns the value into a decoder in Dictionary.getDecoder (Dictionary.java:899-908 on main 645d4fe14a20): its own ISO8859_14Decoder for the exact string ISO8859-14, two exact aliases, otherwise Charset.forName(value). The JDK has no ISO-8859-10 or ISO-8859-14 charset under any of its 963 names and aliases, so SET ISO8859-10 (every spelling tested), SET ISO-8859-14 and SET iso8859-14 make the public constructor throw java.nio.charset.UnsupportedCharsetException, and the dictionary does not load. C Hunspell lower-cases the value, keeps only letters and digits, and has tables for both. The failure is loud and at load time; only the opt-in Hunspell module is involved, and no surveyed public dictionary uses these values. Every release with Hunspell, 3.5.0 to 10.6.0, and main is affected; since 4.8.0 the exact ISO8859-14 loads.
- The public constructors (
:189,:206,:226) end in the last one, which reads the affix prologue and callsreadConfig(...)(:260). readConfig(:837) passes theSETargument on:decoder = getDecoder(singleArgument(reader, line));(:849). The second pass repeats the lookup in its consistency check (:465-470).CHARSET_ALIASES(:889-890) has two exact keys:microsoft-cp1251->windows-1251andTIS620-2533->TIS-620.getDecoder(:899-908):if ("ISO8859-14".equals(encoding)) return new ISO8859_14Decoder();(exact, case-sensitive), then the alias map, thenreplacingDecoder(Charset.forName(encoding))(:907). Its javadoc (:893-894): "Note, This isn't perfect as I think ISCII-DEVANAGARI and MICROSOFT-CP1251 etc are allowed...".ISO8859_14Decoder.java:25: "many hunspell dictionaries use this encoding, yet java does not have it?!?!". There is no ISO-8859-10 counterpart.Charset.forNameresolvesISO-8859-2,ISO8859-2andiso8859-2through JDK aliases. JDK 25.0.3 has 173 charsets with 963 names and aliases, and none normalises toiso885910oriso885914;Charset.isSupportedis false forISO8859-10,ISO-8859-10,ISO8859-14andISO-8859-14.
In Hunspell's normal non-Mozilla build (the code from #ifndef MOZILLA_CLIENT at :682; a separate Mozilla implementation starts at the #else at :2349), get_current_cs (:2326) runs toAsciiLowerAndRemoveNonAlphanumeric (:2305, called at :2328), then looks the result up in encds[] (:2279), which has iso885910 (:2289) and iso885914 (:2294). An unknown name is not an error in C: it warns and falls back to ISO-8859-1 (:2340-2345).
SET line | main 645d4fe14a20 | with the fix |
|---|---|---|
ISO8859-2, ISO-8859-2, iso8859-2 (controls) | load, decode | load, decode |
ISO8859-14 | loads (Lucene's own table) | loads |
ISO-8859-14, iso8859-14 | UnsupportedCharsetException | load |
ISO8859-10 | UnsupportedCharsetException | loads |
Over 32 spellings per value (upper or lower case, crossed with no separator, -, _ or . in two places) on the stock main jar: ISO8859-10 loads in 0, ISO8859-14 in 1 (the exact one), ISO8859-1 to 9, 13 and 15 in 8 to 10 each. Every spelling that loads decodes every letter byte as the unicode.org tables say (10,374 checks, 0 mismatches). With the fix all 64 spellings load (17,746 checks, 0 mismatches).
Exposure and likelihood201 words
- Who is hit. Applications whose affix file says
SET ISO8859-10(Latin-6, Nordic) in any tested spelling, or writes ISO-8859-14 (Latin-8, Celtic) as anything but exactlyISO8859-14. The exception comes where the affix file is read:new Dictionary(...)orHunspellStemFilterFactory.inform.HunspellStemFilterand theHunspellspell-checker take a builtDictionary, so they simply never get one. No default analyzer uses Hunspell. - What happens. The constructor throws
java.nio.charset.UnsupportedCharsetExceptionnaming the SET value. No wrong results, no index damage, nothing persisted, no scale needed. The constructor javadoc names onlyIOExceptionandParseException; throughHunspellStemFilterFactorythe exception arrives unwrapped. The hunt's impact class "crash" means exactly this, an unchecked exception on input the API accepts, here at load time; the JVM keeps running. - How likely. Rare. The finder's survey of the two dictionary collections the package javadoc recommends found 0 of 191 dictionaries using these values (recorded only as the finder's summary; not re-checked). Hunspell's own test data writes a hyphenated
SET ISO-8859-15(1.7.2tests/encoding.aff), which loads in Lucene only because the JDK has that name (the canonical name of its ISO-8859-15 charset). - Workaround. Convert the dictionary to UTF-8 and write
SET UTF-8, or, for ISO-8859-14, write exactlyISO8859-14.
One JUnit method on the public API (Dictionary constructor, Hunspell.spell): a one-line affix file SET ISO8859-10 and a one-word dictionary holding byte 0xBF. No random values of its own; fails with assertions on and off. The same test, unchanged, is the regression test.
/*
* 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.hunspell;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestSetEncodingIso8859_10 extends LuceneTestCase {
public void testSetIso8859_10Loads() throws Exception {
byte[] aff = "SET ISO8859-10\n".getBytes(StandardCharsets.US_ASCII);
byte[] dic = {'1', '\n', 'x', (byte) 0xBF, 'y', '\n'};
Dictionary dictionary;
try {
dictionary =
new Dictionary(
new ByteBuffersDirectory(),
"d",
new ByteArrayInputStream(aff),
new ByteArrayInputStream(dic));
} catch (UnsupportedCharsetException e) {
// expected: SET ISO8859-10, a value hunspell(5) lists, loads; actual: the constructor throws
throw new AssertionError("expected SET ISO8859-10 to load, got " + e, e);
}
assertTrue(
"ISO-8859-10 byte 0xBF should read as U+014B", new Hunspell(dictionary).spell("x\u014By"));
}
}
cp $REPRO/TestSetEncodingIso8859_10.java $LUCENE/lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/
cd $LUCENE && ./gradlew :lucene:analysis:common:test --tests org.apache.lucene.analysis.hunspell.TestSetEncodingIso8859_10 -Ptests.seed=DEADBEEF
- Expected
- The dictionary loads and
spell("x\u014By")returns true: byte 0xBF isŋ(U+014B) in ISO-8859-10. - Observed
- The constructor throws
UnsupportedCharsetException: ISO8859-10, fromCharset.forNameinDictionary.getDecoder.
More files, environment, full output256 words
Copy the test into lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/ of a Lucene checkout and run the gradle command. With the fix it passes unchanged; its assertTrue after loading only matters on a fixed build, where it checks that byte 0xBF decodes as ISO-8859-10.
The seed only picks the framework's locale, timezone and assertion mode (DEADBEEF: en-BW, Asia/Irkutsk, assertions off); seeds DEADBEEF and 1234ABCD give the same result.
Executed: gradle on a fresh 645d4fe14a20 worktree (fails); the standalone runner on main (-ea, -da, two seeds) and on the 10.6.0, 10.5.2 and 9.1.0 release jars (fails, same message, -ea and -da). The ISO-8859-14 and iso8859-14 spellings are left out on purpose: same path, same exception (see the table under "The finding in 30 seconds").
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); also fails on 10.6.0, 10.5.2 and 9.1.0 |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux aarch64 (nothing platform-specific) |
cp $REPRO/TestSetEncodingIso8859_10.java $LUCENE/lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/
cd $LUCENE && ./gradlew :lucene:analysis:common:test --tests org.apache.lucene.analysis.hunspell.TestSetEncodingIso8859_10 -Ptests.seed=DEADBEEF
# gradle, fresh main 645d4fe14a20 worktree + the test, -Ptests.seed=DEADBEEF (2026-10-30)
TestSetEncodingIso8859_10 > testSetIso8859_10Loads FAILED
java.lang.AssertionError: expected SET ISO8859-10 to load, got java.nio.charset.UnsupportedCharsetException: ISO8859-10
at __randomizedtesting.SeedInfo.seed([DEADBEEF:1C956C176F957FE7]:0)
at org.apache.lucene.analysis.hunspell.TestSetEncodingIso8859_10.testSetIso8859_10Loads(TestSetEncodingIso8859_10.java:40)
Caused by:
java.nio.charset.UnsupportedCharsetException: ISO8859-10
at java.base/java.nio.charset.Charset.forName(Charset.java:534)
at org.apache.lucene.analysis.hunspell.Dictionary.getDecoder(Dictionary.java:907)
at org.apache.lucene.analysis.hunspell.Dictionary.readConfig(Dictionary.java:849)
at org.apache.lucene.analysis.hunspell.Dictionary.<init>(Dictionary.java:260)
at org.apache.lucene.analysis.hunspell.Dictionary.<init>(Dictionary.java:213)
at org.apache.lucene.analysis.hunspell.Dictionary.<init>(Dictionary.java:192)
at org.apache.lucene.analysis.hunspell.TestSetEncodingIso8859_10.testSetIso8859_10Loads(TestSetEncodingIso8859_10.java:32)
:lucene:analysis:common:test (FAILURE): 1 test, 1 failure
# standalone runner, seed DEADBEEF (the 9.1.0 -da run: seed FC928A08)
main 645d4fe14a20 -ea / -da tests_run=1 failures=1 same message and cause chain
10.6.0, 9.1.0 -ea / -da tests_run=1 failures=1 same message
10.5.2 -ea / -da tests_run=1 failures=1 same message (getDecoder:919 there)- The Hunspell manual lists
ISO8859-10as a SET value and C Hunspell has its table; nothing in Lucene limits SET names. - Lucene ships its own ISO-8859-14 table but reaches it through one spelling, while other ISO-8859 parts also load as
ISO-8859-nandiso8859-n. - Precedent: the same exception for
SET TIS620-2533(SOLR-2934) was fixed in 2014 with a table entry.
The full argument166 words
- Contract. The constructors read "hunspell affix and dictionary files" and document only
IOExceptionandParseException(Dictionary.java:178-188,:195-205,:216-225). The package is "A Java implementation of Hunspell stemming and spell-checking algorithms" (package-info.java:19-20, link markup dropped). No javadoc, test or CHANGES entry limits SET to JDK charset names. - The manual. hunspell(5) lists
ISO8859-10among the possible SET values (1.3.4 and 1.7.2), and C Hunspell resolves it to its own table after normalising the name. - Lucene adds missing tables on purpose.
ISO8859_14Decoderexists because "many hunspell dictionaries use this encoding, yet java does not have it", but only one exact spelling reaches it. - Precedent. SOLR-2934 (a 2012 comment) reported the same
UnsupportedCharsetExceptionforSET TIS620-2533through the same lookup; LUCENE-5468 (#6531, 2014) fixed that value with theCHARSET_ALIASESentry. - Tests. No upstream test exercises
ISO8859_14Decoderor asserts that a SET name is rejected; the one test aimed at parsing the SET value (TestDictionary.testSetWithCrazyWhitespaceAndBOMs) checks whitespace and BOM handling forUTF-8.
The devil's-advocate check246 words
The strongest case for "works as designed" (rated moderate for ISO8859-10, moderate to strong for the ISO8859-14 spellings): the getDecoder javadoc has admitted since 2011 that SET support "isn't perfect"; the supported set is JDK charsets plus a small table built from real dictionaries (LUCENE-5468's CHANGES entry: the filter "loads all known openoffice dictionaries without error"); the manual writes part 14 only as ISO8859-n, which works; LUCENE-9687 scopes C parity to some languages; failing loudly is safer than C's silent ISO-8859-1 fallback; and in SOLR-2934 the maintainer (Robert Muir) replied the same day that there was "no point in worrying about adding aliases/matching up charset naming for Thai", because the filter would be a no-op with that word-list-only dictionary (the alias came two years later).
Why it fails: the javadoc is hedged ("I think ... etc") and names ISCII-DEVANAGARI and MICROSOFT-CP1251, not these values; ISO8859-10 is a documented value that the public constructor accepts; nothing limits SET names; and the one-spelling special case was already narrower than C Hunspell's normalisation when it was written. Concede that the compatibility contract is implied (by the manual and by constructors that read "hunspell affix and dictionary files"), not written down. Honest caveat: C Hunspell's own ISO-8859-10 table has no case data for the high bytes (for 0xBF it stores {0x00, 0xbf, 0xbf}), so C loads such a dictionary but does not case-map its non-ASCII letters. Verdict: a long-standing compatibility gap with a small fix, not a regression.
Recommended Normalise the special-case names like C Hunspell and add an ISO8859_10Decoder
Match the SET name as C Hunspell does (lower case, letters and digits only): iso885914 gets the existing ISO-8859-14 decoder, iso885910 a new 96-entry ISO-8859-10 one, and the two alias keys match the same way (then 12 failing spellings of those two load); other names reach Charset.forName unchanged. Validated: the repro and the hunspell test package (159 tests) pass.
// C Hunspell matches SET values lower-cased and without non-alphanumerics
// (csutil.cxx get_current_cs)
String normalized = encoding.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "");
if ("iso885914".equals(normalized)) {
return new ISO8859_14Decoder();
}
if ("iso885910".equals(normalized)) {
return new ISO8859_10Decoder();
}All options and trade-offs3 options · 559 words
Three options. The first was validated with gradle in a fresh scratch worktree at 645d4fe14a20; the other two were not executed. C Hunspell's behaviour for unknown names (fall back to ISO-8859-1) is not an option: it would load the file but misread the 46 of the 96 bytes from 0xA0 up where ISO-8859-10 and Latin-1 differ (0xBF would read as ¿, not ŋ).
Normalise the special-case names like C Hunspell and add an ISO8859_10Decoder (validated by the checks) (recommended)
In Dictionary.getDecoder, compute encoding.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", ""); map iso885914 to ISO8859_14Decoder and iso885910 to a new ISO8859_10Decoder (bytes 0xA0-0xFF from the unicode.org 8859-10 mapping, built like ISO8859_14Decoder). The sketch also maps normalised microsoftcp1251 and tis6202533 onto the two existing alias keys, so 12 spellings of those two that fail today (7 for windows-1251, 5 for TIS-620) load too; drop those lines for the narrowest change. Everything else keeps going to the alias map and Charset.forName unchanged. Dictionary.java +13/-1, new ISO8859_10Decoder.java (59 lines).
// C Hunspell matches SET values lower-cased and without non-alphanumerics
// (csutil.cxx get_current_cs)
String normalized = encoding.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "");
if ("iso885914".equals(normalized)) {
return new ISO8859_14Decoder();
}
if ("iso885910".equals(normalized)) {
return new ISO8859_10Decoder();
}Trade-offs
- One more hand-written table to maintain; its 96 entries match an independent ISO-8859-10 codec (96 of 96).
- Like
ISO8859_14Decoder, the new decoder reports ISO-8859-1 as itscharset(), so it inherits LFV-932 (FLAG UTF-8 with an 8-bit SET merges flags); that bug has its own fix site. - Over 1,457 names (all JDK names and aliases plus generated spellings), every name that loads today still loads with the same charset and decoder class, and 75 names that fail today load.
- Open PR #16422 moves the SET call sites into a
switchbut leavesgetDecoderalone; the two should not conflict. The 2011 "isn't perfect" javadoc could be updated in the same change.
What the checks validated
- Gradle, fresh scratch worktree at
645d4fe14a20, JDK 25,-Ptests.seed=DEADBEEF(2026-10-30): without the fix the minimal test fails; withfix.diffapplied it passes unchanged, and the wholeorg.apache.lucene.analysis.hunspelltest package passes with the seed's assertion mode (off) and with-Ptests.asserts=true: 47 suites, 159 tests, 13 skipped, 0 failures each time. The skips (TestAllDictionaries2,TestPerformance10,TestHunspellRepositoryTestCases1) need external data passed as system properties.:lucene:analysis:common:checkGoogleJavaFormatpasses. The rest of the module's tests were not run. - Standalone runner with the fix shadowed: the minimal test passes (
-ea,-da), the supplementary ISO8859-14 spelling test passes 4 of 4 (-ea; no-darun of it with the fix is recorded), and the model's jar check loads all 64 spellings with 0 decode mismatches (17,746 checks). - An independent reviewer's own 4-test JUnit passes 4 of 4 with it (
-ea,-da).
Add if ("ISO8859-10".equals(encoding)) return new ISO8859_10Decoder(); and compare ISO8859-14 case-insensitively. Not executed.
Trade-offs
Fixes the lead case with fewer lines, but ISO-8859-14 and ISO-8859-10 still fail, and the special cases stay inconsistent with the JDK-alias behaviour of the other ISO-8859 parts.
Catch the charset exception in getDecoder and throw a ParseException that names the SET value. Not executed.
Trade-offs
Gives the documented exception type and a clearer message, but the dictionary still does not load: a complement to the first option, not a fix.
Services that accept user-supplied Hunspell dictionaries get a raw UnsupportedCharsetException from HunspellStemFilterFactory at analyzer setup, not its "Unable to load hunspell data!" IOException, so a handler that catches only IOException misses it.
A probe loaded a dictionary through the stock jar for every SET value the Hunspell manual lists, plus spelling variants, and flagged ISO8859-10 and two ISO8859-14 spellings as rejected. A model of C Hunspell's name lookup against Lucene's then mapped the gap over 536 spellings, with 0 disagreements against the real jar.
The method in full178 words
Method: a differential model checked on the real jar (the hunt's "semi-formal" tier; the first signal was the probe, the model came after it).
- First signal. A planning probe loaded a one-word-per-letter-byte dictionary through the stock main jar for every SET value hunspell(5) lists plus 8 spelling variants, and checked every letter byte against the standard mapping tables. It reported
ISO8859-10,ISO-8859-10,ISO-8859-14andiso8859-14as rejected (and the javadoc-acknowledged ISCII-DEVANAGARI and MICROSOFT-CP1251, which this bug leaves out); every other listed value loaded with 0 misdecoded letters. - Model. The finder wrote a model of SET-name acceptance: C Hunspell 1.7.2's name normalisation and encoding table against Lucene's
getDecoder, over the JDK 25 charset registry's 927 case-insensitively distinct names and aliases (963 counted raw). ISO-8859-10 and ISO-8859-14 have no JDK spelling at all. - Check on the jar. 536 spellings through the public constructor: 113 load, 0 disagreements with the model, 10,374 decode checks with 0 mismatches against the unicode.org tables. The finder reduced it to a public-API JUnit; the hunt later minimised that to the one-test repro.
No prior report found; four searches agree, the latest on 2026-10-30: 152 automated queries over GitHub, the JIRA archive, the mailing lists and the web, 62 quoted GitHub searches and all 408 open PRs. Closest: SOLR-2934, the same exception for a different SET value, fixed in 2014.
Searches5 searches
No report of this defect was found. The closest item is a fixed sibling: SOLR-2934 reported the same exception for SET TIS620-2533, which LUCENE-5468 mapped with an alias in 2014. Reading that ticket and the 2011 javadoc together as one known root cause would make this bug "known"; it stays "new" because that value was fixed, the remaining gaps need different code, and no public report names ISO8859-10 or the ISO8859-14 spellings.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues, PRs and commits (apache/lucene, incl. the migrated JIRA archive) | 2026-10-30 | 70 issue/PR and 3 commit queries, plus 62 quoted-phrase searches such as "ISO8859-10", "ISO8859-14", "ISO8859_14Decoder", "CHARSET_ALIASES", "UnsupportedCharsetException" and "Dictionary.getDecoder" | no report; the exact names return 0 hits; #6531 (LUCENE-5468) introduced the exact match without discussing other names |
| Open PRs | 2026-10-30 | changed-file lists of all 408 open apache/lucene PRs | only #16422 touches Dictionary.java; it keeps getDecoder unchanged |
| JIRA (LUCENE, SOLR, LUCENENET) | 2026-10-30 | 17 direct JQL queries and 10 archive queries | SOLR-2934: a 2012 comment reports UnsupportedCharsetException for SET TIS620-2533 (fixed 2014); a precedent, not this bug |
| Mailing lists | 2026-10-30 | 70 queries over dev@, java-user@, issues@, commits@ and the Solr lists | no report; a 2012 solr-user@ mail hit the same lookup with a malformed value ('UTF-8' in quotes), not this bug |
| Downstream and web | 2026-10-30 | downstream repositories (Elasticsearch, OpenSearch, Solr, Lucene.NET), hunspell/hunspell, all of GitHub, 18 web queries | no report |
Present since the first Hunspell code (LUCENE-3414, e3172b9239c, 2011), first released in 3.5.0. LUCENE-5468 (4.8.0) made the exact ISO8859-14 load; ISO8859-10 and the other ISO8859-14 spellings still fail on every release to 10.6.0 and on main.
Versions tested6 rows
Introduced by LUCENE-3414 ("Bring Hunspell for Lucene into analysis module", now #4487; commit subject "LUCENE-3414: Added Hunspell for Lucene"; trunk e3172b9239c, 2011-09-10, shipped through the same-day branch_3x twin 1e084f1d08e; no pull request). Its lookup was a bare Charset.forName(encoding), with the "isn't perfect" javadoc already in place. Partial fix: LUCENE-5468 (#6531, Robert Muir; 2e0fc562bc2, landed as 06173592e3d and 055557d04c9 on 2014-02-27, released in 4.8.0) added ISO8859_14Decoder for the exact ISO8859-14 and the two aliases. No later change alters the SET-name lookup: LUCENE-9570 8ef6a0da568 (a reformat) and LUCENE-9716 8f75933f3da (9.0.0, moved SET and FLAG parsing into readConfig and renamed getJavaEncoding to getDecoder). The lookup has one of three bodies at all 128 release tags with the module, 3.5.0 to 10.6.0 (10.6.0 has the same getDecoder body as 10.5.2 and main), and each body was executed and fails. Upstream main 2f424fd428da (2026-10-30) is unchanged. Old releases were run on JDK 25, not the JDK of their time.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | ISO8859-10, ISO-8859-14, iso8859-14 throw; exact ISO8859-14 and the ISO8859-2 controls load (-ea, -da); the minimal test fails |
| 10.6.0, 10.5.2, 9.1.0 | affected | the minimal test fails (-ea and -da); on 10.5.2 and 10.6.0 the probe gives the same table as main |
| 4.8.0 | current form | exact ISO8859-14 loads (LUCENE-5468); the other three throw |
| 3.5.0, 4.7.2 | wider form | all four values throw, including the exact ISO8859-14 |
| 3.4.0 | no Hunspell | no Hunspell API |
| main + the fix | not affected | all four values load and decode; the minimal test and the hunspell test package pass |
An independent reproducer, an intended-behaviour devil's advocate, cross-model reviews (a reading-only "not real" was procedural; its executed re-run found it real) and an adjudication checked the claim; the adjudication confirmed it. A re-verification on 2026-10-30 reproduced it on main and 10.5.2 with assertions on and off, and the fix passes.
Checks and verdicts424 words
Each check worked from the claim, not from the finder's conclusions. The devil's advocate's "uncertain" is about the label (bug or improvement), not about whether the behaviour is real.
| Check | Verdict | What it found |
|---|---|---|
| finder (differential model) | candidate | Model of C Hunspell's SET lookup against getDecoder, checked on the stock jar over 536 spellings (0 disagreements); its 5-test JUnit fails 3 of 5 on main (-ea, -da), 10.5.2, 9.12.3, branch_10_6 and 8.11.4. |
| Independent reproducer | real | Own 7-test JUnit: 4 of 7 fail on main (-ea, -da), 10.5.2 and 9.12.3; controls pass; 7 of 7 pass with the fix. |
| cross-model, refute lens (reading only) | not real (low, procedural) | The first review by an independent reviewer from another model family (OpenAI GPT-5.6), on 2026-10-28, could not run any command, so it wrote no test and ran no Java; its notes say this "not real" does not disprove the behaviour. A separate re-run of the finder's JUnit failed 3 of 5. The executed refute review below repeated this lens on 2026-10-30 and voted real. |
| Cross-model, intended lens | real (medium) | An independent reviewer from another model family (OpenAI GPT-5.6), reading only: intended argument moderate, root cause agrees, inputs in contract. |
| Intended-behaviour devil's advocate | uncertain, leaning real | Built the strongest "works as designed" case; re-ran the finder's JUnit on main and 10.5.2 (3 of 5 fail). |
| adjudication | confirmed (medium) | Re-ran the independent reproduction's test (-da, 4 of 7 fail) and the finder's (-ea, 3 of 5 fail). |
| cross-model, refute lens (executed) | real (medium) | An independent reviewer from another model family (OpenAI GPT-5.6) wrote its own 4-test JUnit: ISO8859-10 and ISO-8859-14 fail, UTF-8 and the exact ISO8859-14 pass. Re-run on main 645d4fe14a20 twice: 2 of 4 fail (-ea, -da), 0 of 4 with the fix. |
| audits | keep; verified | The credibility audit re-ran the finder's JUnit (3 of 5 fail); the minimal-repro audit ran the one-test repro on 645d4fe14a20 (two seeds, -ea, -da: fails; with the fix: passes). |
| re-verification (2026-10-30) | real | The one test fails on main and 10.5.2 (-ea, -da) and passes with the fix; a supplementary ISO8859-14 spelling test fails 2 of 4 stock and 0 of 4 with the fix. |
| this doc | reproduced; fix validated | Gradle on a fresh 645d4fe14a20 worktree: the minimal test fails; with the fix it passes together with the whole hunspell test package (159 tests), assertions off and on. |
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.
- PNG 463 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,316 words
- Registry title
- Hunspell Dictionary cannot load SET ISO8859-10, or ISO8859-14 spelled any way except exactly "ISO8859-14" (UnsupportedCharsetException at load)
- Area
- analysis / hunspell (Dictionary SET encoding lookup)
- Priority
- Low, rank 616: A loud load-time UnsupportedCharsetException in the opt-in Hunspell module for SET ISO8859-10 or non-exact ISO8859-14 spellings, which no surveyed public dictionary uses and which corrupts nothing.
- Impact class
- crash
- Impact note
- Only applications that use the opt-in Hunspell module (a Dictionary built directly, through HunspellStemFilterFactory, or for the Hunspell spell-checker) with an affix file that says SET ISO8859-10 (any spelling) or ISO8859-14 in any spelling except the exact "ISO8859-14" are hit: the Dictionary constructor throws UnsupportedCharsetException and the dictionary does not load, loudly and at load time, with no wrong results, nothing persisted and no scale needed; the finder's survey found no public dictionary that uses these values (0 of 191, not re-checked), and every release from 3.5.0 to 10.6.0 has it (in 3.5.0-4.7.2 even the exact "ISO8859-14" fails).
- Summary
- You load a Hunspell dictionary whose affix file says SET ISO8859-10 (Latin-6, Nordic), a value the hunspell(5) man page lists among the possible SET values, or says ISO8859-14 (Latin-8, Celtic) spelled ISO-8859-14 or iso8859-14, and expect it to load: C Hunspell lower-cases the SET value and drops non-alphanumerics before its table lookup, and in Lucene every other listed ISO-8859 part loads in its hyphenated and lower-case spellings (ISO-8859-2, iso8859-2, ...). Instead the Dictionary constructor throws java.nio.charset.UnsupportedCharsetException, because Dictionary.getDecoder uses Lucene's own ISO8859_14Decoder only for the exact string "ISO8859-14" and otherwise calls Charset.forName, and the JDK has no ISO-8859-10 or ISO-8859-14 charset under any name. Spellings that do load decode every letter byte correctly, so nothing is silently wrong; the dictionary is refused.
- Introduced
- e3172b9239cda3c53769a9e1dd82ea122178b93b (LUCENE-3414 'Added Hunspell for Lucene', trunk svn r1167467, issue = GitHub #4487, no PR; same-day branch_3x twin 1e084f1d08e48f93edafea047282f5b984655909, svn r1167505, shipped in 3.5.0): the first Hunspell code, whose encoding lookup (getJavaEncoding) was a bare Charset.forName, with the javadoc still on main ('This isn't perfect as I think ISCII-DEVANAGARI and MICROSOFT-CP1251 etc are allowed...'). Partial fix: LUCENE-5468 (#6531; dev-branch commit 2e0fc562bc2 of 2014-02-24, branch_4x 055557d04c9 and trunk 06173592e3d of 2014-02-27, released in 4.8.0) added ISO8859_14Decoder, used only when the SET value equals exactly 'ISO8859-14', and the two CHARSET_ALIASES entries; ISO8859-10 and the other ISO8859-14 spellings were left failing. Later changes are behaviour-preserving (LUCENE-9570 8ef6a0da568 reformat; LUCENE-9716 8f75933f3da rename to getDecoder). [read: git history and blame in the full-history clone; bracketed by execution: 3.4.0 has no Hunspell API, 3.5.0 fails; 4.7.2 fails even for the exact 'ISO8859-14', 4.8.0 loads it] (2011-09-10 (LUCENE-3414; current form since the LUCENE-5468 partial fix of 2014-02-24/27))
- First affected version
- 3.5.0 [exec] (tagged 2011-11-25), the first release with the Hunspell module; 3.4.0 has no Hunspell API [exec]. Current form (only the exact 'ISO8859-14' of the two values loads) since 4.8.0 [exec]
- Affected versions
- Every release that has the Hunspell module, 3.5.0 to 10.5.2, plus 10.6.0 (branch_10_6 4ae946e5a9b, the RC1 revision) and main a9779ef3c193 [exec: 3.5.0, 3.6.2, 4.0.0, 4.7.2, 4.8.0, 5.5.5, 6.6.6, 7.7.3, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 (also -da), branch_10_6 and main (-ea and -da) fail with the follow-up's version-bridging probe or the finder's JUnit, ISO8859-2 controls pass in every run; with the fix shadowed the probe reports the bug absent on main]. Two eras: 3.5.0-4.7.2 fail for every spelling of both values, including the exact 'ISO8859-14'; 4.8.0 onwards fail for every spelling of ISO8859-10 and every spelling of ISO8859-14 except the exact one. [read] the lookup has one of three bodies at all 127 release tags with the module in the history clone (3.5.0-4.7.2 bare Charset.forName; 4.8.0-8.8.1 exact-match plus aliases; 9.0.0-10.5.2 the same logic renamed getDecoder); Dictionary.java on upstream main 645d4fe14a20, branch_11x, branch_10x, branch_10_6 and branch_9_12 keeps the exact-match test and has no ISO-8859-10 handling (2026-10-28). Not fixed upstream. All runs on Linux aarch64 with JDK 25.0.3; older JDKs not tested
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that could only read the code (its low-confidence “not real” is procedural), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- semi-formal
- Upstream and prior reports
- none found (2026-10-28, read-only; the prior-report record, verdict new): scripted prior-report searches of 89 queries (30 GitHub issue/PR, 6 commit, 5 downstream-repository, 28 mailing-list over dev@, java-user@, issues@ and commits@, 10 JIRA-archive, web) and the finder's 53; 51 manual quoted-phrase GitHub searches over apache/lucene, Elasticsearch, OpenSearch, Solr, lucene-solr, Lucene.NET, hunspell/hunspell and all of GitHub; GitHub code search for ISO8859_10Decoder (no Lucene fork or patch has one); the changed-file lists of all 413 open apache/lucene PRs (#16422, a switch refactor that keeps getDecoder unchanged, #16911 and #16929 touch hunspell paths; none changes getDecoder); the 88 apache/lucene issues and PRs updated since 2026-10-26 (none about hunspell encodings); direct LUCENE, SOLR and LUCENENET JIRA queries; the solr-user, users, dev (Solr) and general lists; refresh (2026-10-28T17:4xZ): hunspell PRs and issues updated since 2026-10-26 only #16919 (merged, AffixCondition only), and quoted searches '"ISO8859-10"', 'hunspell "8859-14"' and 'hunspell SET encoding unsupported' 0 hits. Closest items, none this bug: SOLR-2934 (a 2012 comment reported UnsupportedCharsetException for SET TIS620-2533 through the same lookup, a different value that has an alias since 2014: the precedent that an unmapped SET name is fixed with a table entry); the getDecoder javadoc of 2011 ('This isn't perfect as I think ISCII-DEVANAGARI and MICROSOFT-CP1251 etc are allowed...', the original author's acknowledgement in code, not a report); #6531 (LUCENE-5468, introduced ISO8859_14Decoder and the exact match; ISO8859-10 and other spellings not discussed); #6568 (LUCENE-5505, SET whitespace and BOM handling); #10704 (LUCENE-9665, default encoding without SET). Not fixed upstream.
- Lucene commit tested
- main
645d4fe14a20(11.0.0-SNAPSHOT): gradle in a fresh scratch worktree on 2026-10-30 (the minimal test fails; with the fix it passes together with the wholeorg.apache.lucene.analysis.hunspelltest package, assertions off and on) and the standalone runner (-ea,-da, two seeds); the 10.6.0, 10.5.2 and 9.1.0 release jars (fail,-eaand-da); a public-API version probe on 3.4.0, 3.5.0, 4.7.2, 4.8.0, 10.5.2, 10.6.0 and main.Dictionary.javais byte-identical on upstream main2f424fd428da(2026-10-30). OpenJDK 25.0.3 on Linux aarch64 throughout.
- Finding record
a Lucene main checkout lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java:889-908 (CHARSET_ALIASES with two exact keys; getDecoder javadoc 'This isn't perfect as I think ISCII-DEVANAGARI and MICROSOFT-CP1251 etc are allowed...'; exact '"ISO8859-14".equals(encoding)' -> ISO8859_14Decoder, else Charset.forName at :907), :849 (readConfig SET -> getDecoder), :465-470 (SET critical-directive check also calls getDecoder), :186-187, :203-204, :223-224 (constructor javadoc: only IOException and ParseException); ISO8859_14Decoder.java ('many hunspell dictionaries use this encoding, yet java does not have it'); HunspellStemFilterFactory.java:111-117 (catches only ParseException, so the unchecked exception propagates unwrapped [read]); the hunspell package is identical in Lucene main ea57360912ed (git diff empty)
- The finding in 30 seconds
a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java:189, :206, :226, :260, :465-470, :837, :849, :889-890, :893-894, :899-908a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/ISO8859_14Decoder.java:25
- Who is affected
a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java:178-188, :195-205, :216-225 (constructor javadoc: IOException, ParseException only)a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/HunspellStemFilter.java:60, Hunspell.java:55 (take a built Dictionary); package-info.java (recommended dictionary collections)
- Why it looks unintended
a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java:178-188, :195-205, :216-225, :893-894; ISO8859_14Decoder.java:25; package-info.java:19-20a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/TestDictionary.java:304 (testSetWithCrazyWhitespaceAndBOMs); grep of lucene/analysis/common/src/test for ISO8859-14 and 8859_14: no hita Lucene main checkout (645d4fe14a20) lucene/CHANGES.txt:12544-12545 (LUCENE-5468 entry, :12544-12546; the quote is on :12544-12545)
- Diagrams
a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java:889-908
- TestSetEncodingIso8859_10.java (lucene/analysis/common, package org.apache.lucene.analysis.hunspell, 1 test):
repro/LFV-928/src/org/apache/lucene/analysis/hunspell/TestSetEncodingIso8859_10.java(45 lines)
Made in parallel with this doc from the code at 645d4fe14a20 and the executed runs listed under Sources.
assets/LFV-928/bug.png(463 KB)
