All findings · Lucene finding · LFV-928 · hunspell-set-encoding-iso8859-10-and-14-spellings

Hunspell Dictionary cannot load SET ISO8859-10, and loads ISO8859-14 only when spelled exactly so

An affix file that says SET ISO8859-10, an encoding the Hunspell manual lists, makes Lucene's Dictionary constructor throw UnsupportedCharsetException, so the dictionary never loads; ISO8859-14 loads only in that exact spelling. Every release since 3.5.0 (2011) has it.

Summary

Priority
Low, rank 616A 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
crash
Since
3.5.0 (2011), the first release with Hunspellintroduced 2011-09-10 · over 15 years
Affected
every release from 3.5.0 to 10.6.0, and main
Status
documented
Found by
a probe of every SET value the Hunspell manual lists, then a model of C Hunspell's name lookup checked on the stock jar

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

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.

Background: how a Hunspell dictionary names its encoding

  1. 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.
  2. The affix file's SET line names that encoding, for example SET ISO8859-2. The same byte means different letters in different encodings: 0xBF is ŋ in ISO-8859-10 but ¿ in Latin-1.
  3. Lucene reads the SET line first and turns the name into a Java decoder for both files. Without a decoder the dictionary cannot be read at all.
Diagram of the bug. The SET line of an affix file goes into Lucene's getDecoder. Only the exact string ISO8859-14 reaches Lucene's own ISO-8859-14 table. Every other name goes to the JDK's Charset.forName, which resolves the ISO8859-2 spellings but has no ISO-8859-10 or ISO-8859-14 charset, so for ISO8859-10, ISO-8859-14 and iso8859-14 the Dictionary constructor throws UnsupportedCharsetException and the dictionary does not load.
Where the SET name goes: only the exact 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.

The finding, step by step

  1. Write a two-line Hunspell dictionary: the affix file says SET ISO8859-10 (Latin-6, Nordic) and the word list holds one word, the bytes x, 0xBF, y. In ISO-8859-10, byte 0xBF is the letter ŋ.
  2. Load it with new Dictionary(dir, "d", aff, dic). You expect it to load and spell("xŋy") to be true: the Hunspell manual lists ISO8859-1 to ISO8859-10, and C Hunspell reads such a file.
  3. Instead the constructor throws UnsupportedCharsetException: ISO8859-10. Lucene uses its own table only for the exact name ISO8859-14 and hands every other name to the JDK, which has no ISO-8859-10 or ISO-8859-14 charset at all.
  4. So ISO-8859-14 and iso8859-14 fail 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.
  5. 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.

Who is affected

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 code (main 645d4fe14a20, lucene/analysis/common/.../hunspell/Dictionary.java)

  1. The public constructors (:189, :206, :226) end in the last one, which reads the affix prologue and calls readConfig(...) (:260).
  2. readConfig (:837) passes the SET argument on: decoder = getDecoder(singleArgument(reader, line)); (:849). The second pass repeats the lookup in its consistency check (:465-470).
  3. CHARSET_ALIASES (:889-890) has two exact keys: microsoft-cp1251 -> windows-1251 and TIS620-2533 -> TIS-620.
  4. getDecoder (:899-908): if ("ISO8859-14".equals(encoding)) return new ISO8859_14Decoder(); (exact, case-sensitive), then the alias map, then replacingDecoder(Charset.forName(encoding)) (:907). Its javadoc (:893-894): "Note, This isn't perfect as I think ISCII-DEVANAGARI and MICROSOFT-CP1251 etc are allowed...".
  5. ISO8859_14Decoder.java:25: "many hunspell dictionaries use this encoding, yet java does not have it?!?!". There is no ISO-8859-10 counterpart.
  6. Charset.forName resolves ISO-8859-2, ISO8859-2 and iso8859-2 through JDK aliases. JDK 25.0.3 has 173 charsets with 963 names and aliases, and none normalises to iso885910 or iso885914; Charset.isSupported is false for ISO8859-10, ISO-8859-10, ISO8859-14 and ISO-8859-14.

C Hunspell, for comparison (1.7.2, src/hunspell/csutil.cxx)

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).

The numbers

SET linemain 645d4fe14a20with the fix
ISO8859-2, ISO-8859-2, iso8859-2 (controls)load, decodeload, decode
ISO8859-14loads (Lucene's own table)loads
ISO-8859-14, iso8859-14UnsupportedCharsetExceptionload
ISO8859-10UnsupportedCharsetExceptionloads

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 exactly ISO8859-14. The exception comes where the affix file is read: new Dictionary(...) or HunspellStemFilterFactory.inform. HunspellStemFilter and the Hunspell spell-checker take a built Dictionary, so they simply never get one. No default analyzer uses Hunspell.
  • What happens. The constructor throws java.nio.charset.UnsupportedCharsetException naming the SET value. No wrong results, no index damage, nothing persisted, no scale needed. The constructor javadoc names only IOException and ParseException; through HunspellStemFilterFactory the 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.2 tests/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 exactly ISO8859-14.

Minimal repro

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.

TestSetEncodingIso8859_10.java (lucene/analysis/common, package org.apache.lucene.analysis.hunspell, 1 test)45 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.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"));
  }
}
Command
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, from Charset.forName in Dictionary.getDecoder.

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

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").

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT); also fails on 10.6.0, 10.5.2 and 9.1.0
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux aarch64 (nothing platform-specific)
Command (environment)
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

Observed output

# 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)

Why it looks unintended

  • The Hunspell manual lists ISO8859-10 as 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-n and iso8859-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 IOException and ParseException (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-10 among 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_14Decoder exists 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 UnsupportedCharsetException for SET TIS620-2533 through the same lookup; LUCENE-5468 (#6531, 2014) fixed that value with the CHARSET_ALIASES entry.
  • Tests. No upstream test exercises ISO8859_14Decoder or asserts that a SET name is rejected; the one test aimed at parsing the SET value (TestDictionary.testSetWithCrazyWhitespaceAndBOMs) checks whitespace and BOM handling for UTF-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.

Suggested fix

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 its charset(), 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 switch but leaves getDecoder alone; 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; with fix.diff applied it passes unchanged, and the whole org.apache.lucene.analysis.hunspell test 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 (TestAllDictionaries 2, TestPerformance 10, TestHunspellRepositoryTestCases 1) need external data passed as system properties. :lucene:analysis:common:checkGoogleJavaFormat passes. 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 -da run 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).

Narrow variant: an exact ISO8859-10 case and a case-insensitive ISO8859-14 test

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.

Clearer error only

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.

Downstream note

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.

How it was found

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-14 and iso8859-14 as 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.

Prior reports, history and checks

Prior reports

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.

What was searched, when, and what turned up
WhereWhenHowFound
GitHub issues, PRs and commits (apache/lucene, incl. the migrated JIRA archive)2026-10-3070 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 PRs2026-10-30changed-file lists of all 408 open apache/lucene PRsonly #16422 touches Dictionary.java; it keeps getDecoder unchanged
JIRA (LUCENE, SOLR, LUCENENET)2026-10-3017 direct JQL queries and 10 archive queriesSOLR-2934: a 2012 comment reports UnsupportedCharsetException for SET TIS620-2533 (fixed 2014); a precedent, not this bug
Mailing lists2026-10-3070 queries over dev@, java-user@, issues@, commits@ and the Solr listsno report; a 2012 solr-user@ mail hit the same lookup with a malformed value ('UTF-8' in quotes), not this bug
Downstream and web2026-10-30downstream repositories (Elasticsearch, OpenSearch, Solr, Lucene.NET), hunspell/hunspell, all of GitHub, 18 web queriesno report

History

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.

Versions and branches executed, with results
Version or commitVerdictResult
main 645d4fe14a20 affectedISO8859-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 affectedthe 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 formexact ISO8859-14 loads (LUCENE-5468); the other three throw
3.5.0, 4.7.2 wider formall four values throw, including the exact ISO8859-14
3.4.0 no Hunspellno Hunspell API
main + the fix not affectedall four values load and decode; the minimal test and the hunspell test package pass

Independent checks

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.

Each independent check and its verdict
CheckVerdictWhat it found
finder (differential model) candidateModel 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 realOwn 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 realBuilt 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; verifiedThe 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) realThe 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 validatedGradle 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.

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

Finding record

LFV-928, snapshot of 2026-11-06

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 whole org.apache.lucene.analysis.hunspell test 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, -ea and -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.java is byte-identical on upstream main 2f424fd428da (2026-10-30). OpenJDK 25.0.3 on Linux aarch64 throughout.

Lucene and upstream sources

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-908
  • a 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-20
  • a 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 hit
  • a 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

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

Repro files

About the diagrams

Made in parallel with this doc from the code at 645d4fe14a20 and the executed runs listed under Sources.

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.