All findings · Lucene finding · LFV-1170 · field-storedvalue-rejects-non-string-charsequence

A stored field built from a StringBuilder makes addDocument throw since 9.6.0

Since 9.6.0, a stored field whose value is a StringBuilder (or any CharSequence other than String) makes IndexWriter.addDocument throw IllegalStateException: Cannot store value of type class java.lang.StringBuilder, although the public StoredField(String, CharSequence, FieldType) constructor accepts it and 9.5.0 stored it. Only that document is rejected; toString() avoids it.

Summary

Priority
Low, rank 674Since 9.6.0, a stored field built from a StringBuilder, CharsRef or other non-String CharSequence through the two Expert CharSequence constructors makes addDocument throw for every such document, where 7.7.0 to 9.5.x stored the value. The failure is loud, only that document is rejected, toString() avoids it and no public report of it was found in three and a half years, so it is Low.
Impact
minor
Since
9.6.0 (2023, #12116); 9.5.0 stored the valueintroduced 2023-02-06 · about 3 years
Affected
9.6.0 to 9.12.3, 10.0.0 to 10.6.0 and main; not fixed upstream
Status
documented
Found by
semi-formal: a proof-obligation audit of Field's contract, witnessed by an executable repro

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

StoredField and Field have public constructors that take any CharSequence. Since 9.6.0 the indexing chain stores what storedValue() returns, and Field.storedValue() treats only an exact String as text.

Diagram of the bug: a document with new StoredField("f", new StringBuilder("x"), StoredField.TYPE) goes to IndexWriter.addDocument. Field.storedValue() switches on the value's Java class (Integer, Long, Float, Double, BytesRef, StoredFieldDataInput, String); a StringBuilder matches none of them and reaches the default case, which throws IllegalStateException: Cannot store value of type class java.lang.StringBuilder. On 9.5.0 the stored-fields writer used stringValue() and stored "x".
Field.storedValue() picks what to store from the value's Java class: the text "x" as a String is stored, while the same text in a StringBuilder matches no case and reaches the default case, which throws. Up to 9.5.0 both were stored. Select the still to open it at full size. Generated with AI, checked against the code.
  1. You add a document with new StoredField("f", new StringBuilder("x"), StoredField.TYPE). This public (Expert) constructor takes any CharSequence. No setting is needed: a plain new IndexWriterConfig() with the default codec fails.
  2. You expect the field to be stored and read back as "x", as Lucene 7.7.0 to 9.5.0 did.
  3. To store the field, the indexing chain asks Field.storedValue() for the value. That method picks the stored type from the value's Java class and has a text case only for String: a StringBuilder matches no case and reaches the default case, which throws.
  4. You get IllegalStateException: Cannot store value of type class java.lang.StringBuilder from addDocument, with assertions on and off, on 9.6.0 to 10.6.0 and main. A CharsRef value, or new Field("t", new StringBuilder("x"), TextField.TYPE_STORED), fails the same way.
  5. Only that document is rejected: the writer stays open, nothing is corrupted, and passing value.toString() instead avoids the error.

Who is affected

Code that passes a StringBuilder, CharsRef or its own CharSequence to StoredField(String, CharSequence, FieldType) (Expert) or to Field(String, CharSequence, ...) with a stored type such as TextField.TYPE_STORED. Default settings suffice; every such document fails, loudly. String values and indexed-only fields are not hit.

Mechanism, terms and numbers454 words

StoredField(String, CharSequence, FieldType) and Field(String, CharSequence, IndexableFieldType) are public constructors that accept any CharSequence. Since 9.6.0 (#12116), the indexing chain stores what IndexableField.storedValue() returns; for a Field that is Field.storedValue(), which picks the stored type from the value's runtime class (on main a switch with arms for Integer, Long, Float, Double, BytesRef, StoredFieldDataInput and exactly String, Field.java:608-624 at 5adff78bb168). A StringBuilder, CharsRef or an application's own CharSequence reaches the default arm, so IndexWriter.addDocument throws IllegalStateException: Cannot store value of type class java.lang.StringBuilder for every such document, with assertions on and off. Up to 9.5.0 Lucene's default stored-fields writer stored stringValue(), which converts any CharSequence, and the same document was stored and read back. The exception comes before the aborting stored-fields write, so only the document is rejected and the writer stays usable. Affected: 9.6.0 to 10.6.0 and main.

The mechanism (main 5adff78bb168, lucene/core/src/java/org/apache/lucene/)

  1. Both constructors keep any CharSequence. StoredField(String, CharSequence, FieldType) (document/StoredField.java:147-157) calls Field(String, CharSequence, IndexableFieldType) (document/Field.java:230-257), which checks only for nulls and for a type that is neither indexed nor stored, and keeps the value as is (fieldsData = value, :255). stringValue() turns any CharSequence into a String (:263-270).
  2. Storing asks storedValue(). For a stored field, IndexingChain.invertAndStore calls field.storedValue() (index/IndexingChain.java:1492, after inverting the field if it is also indexed) and writes what it returns.
  3. Only String is text. Field.storedValue() (document/Field.java:608-624) switches on the runtime class of the value: Integer, Long, Float, Double, BytesRef, StoredFieldDataInput, String (:620). Everything else reaches default -> throw new IllegalStateException("Cannot store value of type " + fieldsData.getClass()) (:621-622, the fix site).
  4. Only the document is rejected. The throw comes before the aborting try around storedFieldsConsumer.writeField (IndexingChain.java:1504-1509). A probe on main added three such documents (a StringBuilder and a CharsRef through StoredField, a StringBuilder through Field with TextField.TYPE_STORED): each threw, then isOpen=true tragic=null, and a following String document was added and read back. As with any non-aborting per-document failure, the rejected document is marked deleted internally (index/DocumentsWriterPerThread.java:283-306, :392-414).

Before 9.6.0

Each stored-fields writer chose the type itself; Lucene's default one took numericValue(), else binaryValue(), else stringValue() (Lucene90CompressingStoredFieldsWriter.writeField, :270-300 at the parent of 96136b42821). So a StringBuilder was stored as text. #12116 moved that choice into Field.storedValue() and kept a text arm only for String.

Not hit

String values (all sugar classes such as TextField, StringField and StoredField(String, String) take a String), and indexed-only CharSequence fields: those are tokenized from stringValue() and never call storedValue().

Exposure and likelihood135 words
  • Who. Application code that hands a non-String CharSequence to one of the two CharSequence constructors with a stored type, for example text assembled in a StringBuilder, or a CharsRef.
  • Default vs opt-in. No setting is involved: a plain new IndexWriterConfig() with the default codec fails (executed, probe). The trigger is an opt-in call shape: the CharSequence overloads (the StoredField one is marked Expert) with a value that is not a String.
  • Effect. Every such document is rejected with IllegalStateException. Nothing is corrupted or lost without notice, and the writer stays usable; value.toString() avoids it.
  • Scale. None needed: one document with one field.
  • Likelihood. Rare: the developer sees the exception on the first such document, and our searches found no report in the three and a half years since 9.6.0. Priority Low.

Minimal repro

One JUnit test method on the public API: one document with new StoredField("f", new StringBuilder("x"), StoredField.TYPE), addDocument, then read the value back. Single thread; it fails with assertions on and off under every recorded seed.

TestStoredFieldCharSequence.java (lucene/core, package org.apache.lucene.document, 1 test)38 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.document;

import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestStoredFieldCharSequence extends LuceneTestCase {

  public void testStoreStringBuilderValue() throws Exception {
    try (Directory dir = newDirectory();
        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
      Document doc = new Document();
      doc.add(new StoredField("f", new StringBuilder("x"), StoredField.TYPE));
      // expected: "x" is stored; actual: addDocument throws IllegalStateException
      w.addDocument(doc);
      try (DirectoryReader r = DirectoryReader.open(w)) {
        assertEquals("x", r.storedFields().document(0).get("f"));
      }
    }
  }
}
Command
cp $REPRO/TestStoredFieldCharSequence.java $LUCENE/lucene/core/src/test/org/apache/lucene/document/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.document.TestStoredFieldCharSequence -Ptests.seed=DEADBEEF
# same failure with -Ptests.asserts=false
Expected
The document is added and its stored field reads back as "x", as on 9.5.0.
Observed
addDocument throws IllegalStateException: Cannot store value of type class java.lang.StringBuilder at Field.storedValue(Field.java:622), with assertions on and off.

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

More files, environment, full output193 words

TestStoredFieldCharSequence (one method, extends LuceneTestCase, package org.apache.lucene.document of the lucene/core tests). It carries the Apache license header and passes checkGoogleJavaFormat; as the regression test it also fits as one method in TestField. The test framework randomizes the directory and codec; the test failed under every recorded seed: DEADBEEF (gradle), 1D0A640B9F1714F5 and 2A4B7F3CB493B1DD (the hunt's copy, which differs only in one comment line), D1A6001, D1A6002 and 5EED1170. F1E1D001 and F1E1D002 ran the hunt's code-identical earlier copy TestStoredFieldCharSequenceValue and failed too. With the one-arm fix it passes unchanged.

Environment

lucenemain 5adff78bb168 (12.0.0-SNAPSHOT): fails; 9.6.0, 9.12.3, 10.6.0 (and 10.5.2 with an independent test): fail; 9.5.0: passes
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux aarch64 (pure Java; only this platform was run)
Command (environment)
cp $REPRO/TestStoredFieldCharSequence.java $LUCENE/lucene/core/src/test/org/apache/lucene/document/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.document.TestStoredFieldCharSequence -Ptests.seed=DEADBEEF

Observed output

# gradle, scratch worktree at 5adff78bb168, -Ptests.seed=DEADBEEF
TestStoredFieldCharSequence > testStoreStringBuilderValue FAILED
    java.lang.IllegalStateException: Cannot store value of type class java.lang.StringBuilder
        at org.apache.lucene.document.Field.storedValue(Field.java:622)
        at org.apache.lucene.index.IndexingChain.invertAndStore(IndexingChain.java:1492)
        at org.apache.lucene.index.IndexingChain.processField(IndexingChain.java:1457)
        at org.apache.lucene.index.IndexingChain.processDocument(IndexingChain.java:656)
        at org.apache.lucene.index.DocumentsWriterPerThread.updateDocuments(DocumentsWriterPerThread.java:291)
        at org.apache.lucene.index.DocumentsWriter.updateDocuments(DocumentsWriter.java:428)
        at org.apache.lucene.index.IndexWriter.updateDocuments(IndexWriter.java:1631)
        at org.apache.lucene.index.IndexWriter.updateDocument(IndexWriter.java:1913)
        at org.apache.lucene.index.IndexWriter.addDocument(IndexWriter.java:1492)
        at org.apache.lucene.document.TestStoredFieldCharSequence.testStoreStringBuilderValue(TestStoredFieldCharSequence.java:32)

# -Ptests.asserts=false: the same failure

# with the one-arm fix: PASS with assertions on and off

Why it looks unintended

  • StoredField's constructor documents @param value CharSequence value and throws only for null arguments; storing is that class's only job.
  • IndexableField.storedValue() must return a non-null value for a stored field; Field throws instead, for a value its own constructor accepted.
  • Lucene 7.7.0 to 9.5.0 stored these values; #12116, CHANGES.txt and MIGRATE.md announce no restriction.
The full argument184 words
  • Contract. StoredField(String, CharSequence, FieldType): "@param value CharSequence value", "@throws IllegalArgumentException if the field name, value or type is null" (StoredField.java:151, :153). Field(String, CharSequence, IndexableFieldType) documents @throws only for nulls and for invalid type combinations (Field.java:236-238). IndexableField.storedValue() "must return a non-null value if the field stored" (index/IndexableField.java:74-78).
  • History. 7.7.0, 8.11.4 and 9.5.0 store the value (executed). #12116's commit message lists its goals: find a missing value early, and separate the stored value from the doc-values value; nothing about dropping CharSequence values. On the PR, Robert Muir described it as moving "type-guessing" into "the one place that should be doing it, which is the generic Field.java" (#12116 comment, 2023-01-27). CHANGES.txt (lucene/CHANGES.txt:3623-3625) says only "Introduce IndexableField#storedValue() to expose the value that should be stored to IndexingChain without needing to guess the field's type"; MIGRATE.md has no entry.
  • Tests. No test asserts the rejection: the message appears only in Field.java, and no core or test-framework test stores a non-String CharSequence (the finder's grep).
  • Accepted, then rejected. The value passes the constructor and fails only at addDocument.
The devil's-advocate check199 words

The strongest case for "intended": both constructors are Expert; Field's constructor javadoc still says "Create field with String value" and "@param value string value" (Field.java:231, :234); the class javadoc lists text values as "String, Reader or pre-analyzed TokenStream" (:53-54); StoredValue's only text type is STRING, built from a String (StoredValue.java:32-47, :95-98); the overloads came from a Solr read-side change (SOLR-12885, c651cbfd9a8), and in 2019, reviewing LUCENE-8805 (#9849), Adrien Grand noted that Solr's document fetcher can "pass the String directly" instead of wrapping it in a ByteArrayUtf8CharSequence. The failure is loud, the workaround trivial, and our searches found no report in the three and a half years since 9.6.0.

Why it fails: StoredField's own javadoc promises a CharSequence value for a class whose only job is storing, nothing documents a restriction, and an unannounced change in a minor release turned a working call into an exception. In our reading the accept-then-reject behaviour is what needs fixing, but narrowing or documenting the API instead of adding the arm (Suggested fix, options B and C) is a legitimate maintainer choice. The argument bears on likelihood, hence priority Low.

Suggested fix

Recommended Store any CharSequence as its String (fix A)

Add one case after the String case in Field.storedValue(): case CharSequence cs -> new StoredValue(cs.toString());. It restores what Lucene's default stored-fields writer did up to 9.5.0. The repro passes with assertions on and off, and the closest suites (65 classes, 525 tests, 0 failures, 28 skipped by their suites) pass.

      case String s -> new StoredValue(s);
      case CharSequence cs -> new StoredValue(cs.toString());
      default ->
          throw new IllegalStateException("Cannot store value of type " + fieldsData.getClass());
All options and trade-offs3 options ยท 331 words

Option A was built and checked through gradle on 5adff78bb168; B and C were not run. A maintainer may prefer B, which moves the error to construction time; either way the current accept-then-reject behaviour goes away.

Store any CharSequence as its String (fix A) (validated by the checks) (recommended)

One arm after case String s in Field.storedValue() (Field.java:620). The text is copied into a String when the document is added, as stringValue() did for the stored-fields writer before 9.6.0.

      case String s -> new StoredValue(s);
      case CharSequence cs -> new StoredValue(cs.toString());
      default ->
          throw new IllegalStateException("Cannot store value of type " + fieldsData.getClass());

Trade-offs

The arm must stay after case String s: a String is a CharSequence, and Java rejects a case label dominated by an earlier one. It relies on toString() returning the text, as stringValue() already does. The existing stored-string length check (IndexingChain.java:1495-1502) then applies to these values too. The repro passes unchanged as the regression test.

What the checks validated

  • Gradle in a scratch worktree at 5adff78bb168, seed DEADBEEF: the repro fails without the fix (assertions on and off) and passes with it (on and off).
  • With the fix: the repro, all of org.apache.lucene.document.*, TestIndexableField, TestFieldsReader, TestStoredFieldsConsumer, TestSortingStoredFieldsConsumer and TestLucene90StoredFieldsFormat: 65 classes, 525 tests, 0 failures, 28 skipped by their suites. :lucene:core:checkGoogleJavaFormat passes.

Narrow the API instead (not built)

Reject a non-String CharSequence for a stored type in the constructors, or deprecate the CharSequence overloads (main is 12.0, where deprecated APIs are being removed).

Trade-offs

Fails at construction instead of at addDocument, but it breaks callers that store such values; deprecating the overloads would also drop indexed-only CharSequence fields, which work today. The regression test would have to expect the exception.

Document the restriction only (not built)

A javadoc line on both constructors saying a stored value must be a String.

Trade-offs

Smallest change, but the constructors still accept what addDocument rejects.

Downstream note

Downstream, Solr met the same 9.6.0 change in two of its own field classes (an anonymous Field subclass in EnumFieldType and its IndexableField LargeLazyField) and adapted them on its side by overriding or implementing storedValue() (SOLR-16799), without a report to Lucene; other downstream products were not surveyed.

How it was found

A proof-obligation audit of Field's contract: a public constructor that accepts any CharSequence for a stored type obliges storedValue() to return a value for it. The switch in storedValue() covers only String; a one-document public-API JUnit test then witnessed the exception.

The method in full104 words

Method: semi-formal (a proof-obligation audit of a class's contract, witnessed by an executable repro).

  • The finder read the CharSequence constructors of Field and StoredField against IndexableField.storedValue()'s obligation ("must return a non-null value if the field stored") and checked which runtime classes storedValue() handles. Every non-String CharSequence falls to the default arm.
  • The finder then wrote a public-API JUnit test that fails on main with assertions on and off and on the 10.6.0 and 9.6.0 jars, and passes on 9.5.0.
  • An independent reproducer, two executed cross-model reviews, a devil's-advocate verifier and an adjudication reproduced it with their own tests ("Prior reports, history and checks").

Prior reports, history and checks

Prior reports

No matching report in the documented searches of apache/lucene issues, PRs and commits (with the JIRA archive), the Lucene and Solr mailing lists, downstream trackers and the web (2026-11-03). The nearest item, SOLR-16799, is Solr adapting two of its own field classes to the same 9.6.0 change.

Searches3 searches

No matching public report was found in the documented searches. Nearest items, none a report of this bug: #12116 (the introducing change; its body, comments and reviews never mention CharSequence, StringBuilder or CharsRef); SOLR-16799 / apache/solr#1664 (Solr's Lucene 9.6 upgrade overrode or implemented storedValue() in two of its own field classes, an anonymous Field subclass in EnumFieldType and its IndexableField LargeLazyField: same change, different symptom and fix site, not a report to Lucene); a java-user@ thread of 2025-02-10 with the same message for a float[] value, which no constructor promises to store; #14213, #12142, #9913 and #9506 (same classes, other topics). Upstream main 1c2072f0284 (2026-11-02) still has only the String arm. Limits: keyword search; GitHub search does not index review comments (read directly for #12116, #12139 and #14213); private reports are not visible.

What was searched, when, and what turned up
WhereWhenHowFound
GitHub issues, PRs and commits (apache/lucene incl. the migrated JIRA archive)2026-11-03 (the finder and a follow-up), re-checked the same daytwo scripted prior-report searches (16 issue/PR, 3 commit, 2 downstream, 20 mailing-list and 4 JIRA queries each) and 32 manual searches, e.g. "Cannot store value of type" (0 hits), storedValue CharSequence (0), StoredField CharSequence (only #9913); the body, comments and reviews of #12116, #12139, #14213 and #12142no report; #12116 is the introducing change
Open PRs2026-11-03the changed files of all 409 open apache/lucene PRs, and of the 27 updated since 2026-11-01none touches Field.java, StoredField.java, StoredValue.java or IndexableField.java
Mailing lists, JIRA, downstream, web2026-11-03dev@, java-user@, issues@ and users@ (Lucene and Solr) with phrase queries; JIRA LUCENE and SOLR; the Elasticsearch, OpenSearch and Solr trackers; web search: 2 recorded scripted queries, plus 5 that the follow-up ran without saving resultsSOLR-16799 (Solr-side adaptation) and a 2025 java-user@ thread about a float[] value; neither is this bug

History

Introduced by #12116 (96136b42821, Adrien Grand, 2023-02-06; first release 9.6.0). 7.7.0, 8.11.4 and 9.5.0 store the value; 9.6.0, 9.12.3, 10.5.2, 10.6.0 and main fail. Not fixed upstream.

Versions tested7 rows

Introduced by 96136b42821 on main and its branch_9x cherry-pick 84dcaa850b1: #12116 "Improve document API for stored fields." (Adrien Grand, 2023-02-06; CHANGES.txt credits Adrien Grand and Robert Muir). It added Field.storedValue() with an instanceof String arm only and made the indexing chain store that value. First affected release 9.6.0.

Later edits kept the String-only arm: f12e44b6515 (#14213, the StoredFieldDataInput arm) and 1518164afa3 (#15874, main only, rewrote it as a pattern switch). The CharSequence constructors are older: c651cbfd9a8 and its branch_7x backport 0512ff2e47e (SOLR-12885, 2018), first released in 7.7.0. On the 9.6.0 jars, shadowing only Field.java with the one added arm turns the failure into a pass, which ties the regression to the method #12116 added. Not fixed upstream: main 1c2072f0284 (2026-11-02) still has only the String arm.

Versions and branches executed, with results
Version or commitVerdictResult
main 5adff78bb168 affectedgradle: fails with assertions on and off; passes with fix A
9.6.0, 9.12.3, 10.6.0 affectedthe finder's JUnit test (the same steps as the repro): IllegalStateException: Cannot store value of type class java.lang.StringBuilder, assertions on and off
10.5.2 independent testthe independent reproducer's test (StringBuilder("abc")): the same exception
9.6.0 + one-arm fix shadowed Field.javathe finder's test passes
9.5.0 not affectedthe value is stored and read back as "x"
7.7.0, 8.11.4 main() formthe same steps store the value and read it back
7.6.0 no such constructorcompile error: the CharSequence constructors do not exist yet

Independent checks

A finder, an independent reproducer, two executed cross-model reviews (OpenAI GPT-5.6, intended and refute lenses), a devil's-advocate verifier, an adjudication, an audit and a final repro check reproduced it and agree on the root cause. This doc's gradle runs reproduced it on main and validated the fix.

Checks and verdicts310 words

The cross-model reviews ran on main ea57360912ed, where Field.java, StoredField.java and StoredValue.java are byte-identical to 5adff78bb168; the refute-lens test was re-run on 5adff78bb168 with the same result. The other checks on main ran on 5adff78bb168; the release runs used each release's own jars.

Each independent check and its verdict
CheckVerdictWhat it found
finder reproducedIts one-test JUnit fails on main (assertions on and off), 10.6.0 and 9.6.0, passes on 9.5.0 and with the fix; a probe adds CharsRef and Field + TextField.TYPE_STORED and shows the writer stays open.
cross-model review, intended lens (OpenAI GPT-5.6, executed) real (high)Own tests fail on main, pass on 9.5.0, fail on 9.6.0; both entry points reject while the writer survives; intended-behaviour case moderate.
independent reproducer realOwn test (StringBuilder("abc")) fails on main (on and off), 10.6.0, 10.5.2, 9.12.3 and 9.6.0; passes on 9.5.0 and with the fix.
cross-model review, refute lens (OpenAI GPT-5.6, executed) real (high), could not refuteOwn custom-CharSequence test fails through both entry points; the String and indexed-only controls pass; 9.5.0 passes, 9.6.0 fails.
devil's-advocate verifier realOwn test (Field + TextField.TYPE_STORED over a CharsRef) fails on main and 10.6.0, passes on 9.5.0; the intended case is moderate at best.
adjudication confirmed (high), LowRe-ran the reproducer's test on main with assertions on and off (fail) and with the fix (pass).
separate re-run, audit and final repro check confirmed (fail, fail, pass with fix)The one-test repro fails on main with assertions on and off; the final check set the impact class to minor (loud, per document, Expert constructors, trivial workaround).
this doc reproduced; fix A validatedGradle: fails without the fix; with it the repro and the closest suites pass (65 classes, 525 tests, 0 failures, 28 skipped by their suites).

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

Finding record

LFV-1170, snapshot of 2026-11-06

Registry title
Field.storedValue() handles a String but no other CharSequence, so a stored field built from a StringBuilder, CharsRef or other CharSequence through the public StoredField(String, CharSequence, FieldType) or Field(String, CharSequence, IndexableFieldType) constructor makes IndexWriter.addDocument throw IllegalStateException 'Cannot store value of type ...' (regression in 9.6.0 from #12116)
Area
document / stored values (Field.storedValue, StoredField)
Priority
Low, rank 674: Since 9.6.0, a stored field built from a StringBuilder, CharsRef or other non-String CharSequence through the two Expert CharSequence constructors makes addDocument throw for every such document, where 7.7.0 to 9.5.x stored the value. The failure is loud, only that document is rejected, toString() avoids it and no public report of it was found in three and a half years, so it is Low.
Impact class
minor
Impact note
Applications on 9.6.0 through 10.6.0 or main that build a stored field from a non-String CharSequence (StringBuilder, CharsRef or their own CharSequence) through the Expert StoredField(String, CharSequence, FieldType) overload or the Expert Field(String, CharSequence, IndexableFieldType) constructor with a stored type (for example TextField.TYPE_STORED) get an IllegalStateException from IndexWriter.addDocument for every such document, with -ea and -da and the default IndexWriterConfig, codec and directory; 7.7.0 to 9.5.x stored these values; the failure is loud and only that document is rejected (the writer stays open with no tragic exception), nothing is corrupted, value.toString() avoids it, and no scale is needed.
Summary
You add a document holding new StoredField("f", new StringBuilder("x"), StoredField.TYPE) to an IndexWriter; you expect the value to be stored and read back as "x", since the public constructor takes a CharSequence and Lucene 7.7.0 to 9.5.x did exactly that. Instead addDocument throws IllegalStateException 'Cannot store value of type class java.lang.StringBuilder', because Field.storedValue(), added in 9.6.0 by #12116, has a text branch for String only and sends every other CharSequence to its error branch. The same happens for a Field with a stored type such as TextField.TYPE_STORED over a StringBuilder or CharsRef; only that document is rejected, and value.toString() works around it.
Introduced
96136b42821205e942f21a26c4992530c6909aca on main and its branch_9x cherry-pick 84dcaa850b1646ea50de8292e8993a3dd382cc2c: apache/lucene#12116 'Improve document API for stored fields.' (author @jpountz; CHANGES.txt credits Adrien Grand and Robert Muir). It added Field.storedValue() with arms for Integer, Long, Float, Double, BytesRef and exactly String, throwing IllegalStateException otherwise, and IndexingChain began storing storedValue() instead of letting the stored-fields writer take stringValue(), which converts any CharSequence [read: git log -S 'Cannot store value of type' -- Field.java returns only 96136b42821; git log -L on storedValue() lists 96136b42821, f12e44b6515 and 1518164afa3] [exec: the repro passes on 9.5.0 and fails on 9.6.0; on the 9.6.0 jars, shadowing only Field.java with one added CharSequence branch makes it pass]. Later commits kept the String-only branch: f12e44b6515 (#14213, added the StoredFieldDataInput arm) and 1518164afa3 (#15874, main only, rewrote the chain as a pattern switch, same arms). The CharSequence constructors themselves came from c651cbfd9a8 (SOLR-12885, first release 7.7.0), which stored such values through stringValue(). (2023-02-06 (author date of 96136b42821 and of the branch_9x cherry-pick 84dcaa850b1, 2023-02-06T16:08:13Z; PR #12116 merged 2023-02-06T16:08:14Z))
First affected version
9.6.0 [exec: the one-test repro fails on 9.6.0 with -ea and -da and passes on 9.5.0 with -ea and -da] [read: 84dcaa850b1 is in releases/lucene/9.6.0 and not in 9.5.0]
Affected versions
9.6.0 through 9.12.3, 10.0.0 through 10.6.0 (released 2026-10-29, 4ae946e5a9b), and main 5adff78bb168 (12.0.0-SNAPSHOT) [read: git tag --contains puts 84dcaa850b1 in releases/lucene/9.6.0 .. 9.12.3 and 96136b42821 in releases/lucene/10.0.0 .. 10.6.0] [exec: the finder's one-test JUnit fails with -ea and -da on 9.6.0, 9.12.3, 10.6.0 and main; the independent reproduction's own test fails on 10.5.2, 9.12.3, 9.6.0 and 10.6.0 (-da) and on main (-ea, -da); the intended-behaviour check's own test (Field with TextField.TYPE_STORED over a CharsRef) fails on main (-ea, -da) and 10.6.0 (-da)]. Not affected [exec]: 9.5.0 (repro passes with -ea and -da), 8.11.4 and 7.7.0 (a main() probe of the same steps stores the value and reads it back); 7.6.0 and earlier have no CharSequence constructor (compile error). Not fixed upstream: main tip 1c2072f0284 (2026-11-02) still has only 'case String s' before the default branch at Field.java:620-622, and no open PR touches Field.java, StoredField.java or StoredValue.java. Not new in 10.6. All runs used JDK 25.0.3 on one aarch64 Linux host.
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: proof-obligation audit
Upstream and prior reports
none found (2026-11-03: a scripted prior-report search in two phrasings (32 apache/lucene issue/PR queries, 6 commit queries, 4 downstream-repo queries over Elasticsearch, OpenSearch and Solr, 40 queries over the dev@, java-user@, issues@ and commits@ lists, 8 JIRA-archive queries, 2 web queries); 32 manual GitHub searches including Solr, Elasticsearch, OpenSearch and items updated since 2026-11-01; direct reads of the body, comments, reviews and review comments of #12116, #12139, #14213, #12142, #9913 and others (no mention of CharSequence, StringBuilder or CharsRef); the changed files of all 409 open apache/lucene PRs (none touches Field.java, StoredField.java, StoredValue.java or IndexableField.java; the 5 that touch IndexingChain.java change no storedValue or CharSequence line); lists.apache.org phrase queries on the Lucene and Solr lists; 8 JIRA REST queries over LUCENE and SOLR; 5 web queries; the finder's, the independent reproduction's, the intended-behaviour check's and the adjudication's searches agree). Rechecked the same day: '"Cannot store value of type"' and 'storedValue CharSequence' have 0 hits in apache/lucene, 'StoredField CharSequence' only #9913, 'StoredField StringBuilder' only #12968 and #9450 (unrelated), no issue or PR mentioning storedValue or a stored CharSequence was updated since 2026-11-01, none of the 27 open PRs updated since then changes Field.java, StoredField.java, StoredValue.java, IndexableField.java or IndexingChain.java, and upstream main 1c2072f0284 still has only the String arm in Field.storedValue(). Nearest items, none of them this bug: #12116 (the introducing change; nothing in it discusses CharSequence values), SOLR-16799 / apache/solr#1664 (Solr's Lucene 9.6 upgrade overrode storedValue() in two of its own Field subclasses: same introducing change, different symptom and fix site, not a report to Lucene), the java-user@ thread 'How to retrieve vectors from the IndexReader' (2025-02-10; the same message for a float[] value, which no constructor promises to store), #14213 (added the StoredFieldDataInput arm), #12142 (open design follow-up), #9913 (LUCENE-8870, open since 2019, numeric values in Field) and #9506 (LUCENE-8460, 2018, StoredField null checks)
Lucene commit tested
main 5adff78bb168 (2026-11-02) through gradle in a scratch worktree with assertions on and off, without and with the one-arm fix (the repro, all of org.apache.lucene.document.*, TestIndexableField, TestFieldsReader, TestStoredFieldsConsumer, TestSortingStoredFieldsConsumer, TestLucene90StoredFieldsFormat, checkGoogleJavaFormat), and standalone on the main jars. The hunt ran its test on the 9.5.0 jars (pass) and the 9.6.0, 9.12.3 and 10.6.0 jars (fail), the independent reproducer's test on 10.5.2 (fail), and a main() with the same steps on 7.7.0 and 8.11.4 (stored); 7.6.0 has no such constructor. Only Linux aarch64 with OpenJDK 25.0.3 was run.

Lucene and upstream sources

Finding record
  • lucene/core/src/java/org/apache/lucene/document/Field.java:33-61 (at 5adff78bb168) (class javadoc 'Expert: directly create a field for a document'; text values listed as 'String, Reader or pre-analyzed TokenStream')
  • lucene/core/src/java/org/apache/lucene/document/Field.java:230-257 (at 5adff78bb168) (public Field(String, CharSequence, IndexableFieldType): javadoc 'Create field with String value'; @throws only for null arguments and a type that is neither indexed nor stored; keeps any CharSequence as fieldsData)
  • lucene/core/src/java/org/apache/lucene/document/Field.java:259-275 (at 5adff78bb168) (stringValue() returns fieldsData.toString() for any CharSequence; getCharSequenceValue())
  • lucene/core/src/java/org/apache/lucene/document/Field.java:607-624 (at 5adff78bb168) (storedValue(): pattern switch with arms for Integer, Long, Float, Double, BytesRef, StoredFieldDataInput and exactly String at :620; default branch throws IllegalStateException 'Cannot store value of type ' at :621-622)
  • lucene/core/src/java/org/apache/lucene/document/StoredField.java:147-157 (at 5adff78bb168) ('Expert: allows you to customize the FieldType', '@param value CharSequence value', @throws only for null arguments)
  • lucene/core/src/java/org/apache/lucene/document/StoredValue.java:32-46 (at 5adff78bb168) and :95-96 (closed type set; the only text type is STRING, built from a String)
  • lucene/core/src/java/org/apache/lucene/index/IndexableField.java:74-78 (at 5adff78bb168) (storedValue(): 'must return a non-null value if the field stored')
  • lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1491-1509 (at 5adff78bb168) (storedValue() taken at :1492, before the aborting try around storedFieldsConsumer.writeField at :1504-1509, so the exception rejects only the document)
  • lucene/CHANGES.txt:3623 (at 5adff78bb168) (9.6.0 entry for #12116: 'Introduce IndexableField#storedValue() to expose the value that should be stored ... without needing to guess the field's type'; no note that non-String CharSequence values stop being storable)
The finding in 30 seconds
  • lucene/core/src/java/org/apache/lucene/document/StoredField.java:147-157 (at 5adff78bb168)
  • lucene/core/src/java/org/apache/lucene/document/Field.java:230-257, (at 5adff78bb168) :263-270, :608-624
  • lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1491-1510 (at 5adff78bb168)
Who is affected
  • lucene/core/src/java/org/apache/lucene/document/StoredField.java:147-148 (at 5adff78bb168) ("Expert")
Why it looks unintended
  • lucene/core/src/java/org/apache/lucene/document/StoredField.java:147-157 (at 5adff78bb168)
  • lucene/core/src/java/org/apache/lucene/document/Field.java:53-54, (at 5adff78bb168) :230-257
  • lucene/core/src/java/org/apache/lucene/index/IndexableField.java:74-78 (at 5adff78bb168)
  • lucene/core/src/java/org/apache/lucene/document/StoredValue.java:32-47, (at 5adff78bb168) :95-98
  • lucene/CHANGES.txt:3623-3625; (at 5adff78bb168) lucene/MIGRATE.md (no entry)
  • https://github.com/apache/lucene/pull/12116 (Robert Muir's comment of 2023-01-27, read 2026-11-03)
  • https://github.com/apache/lucene/issues/9849 (LUCENE-8805, Adrien Grand's comment of 2019-05-20, read 2026-11-03)
  • grep of Lucene main 5adff78bb168 lucene/ for "Cannot store value of type" (Field.java:622 only)
Suggested fix
  • lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1495-1502 (at 5adff78bb168) (stored-string length check)
Diagrams
  • lucene/core/src/java/org/apache/lucene/document/Field.java:608-624 (at 5adff78bb168)
  • lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1491-1510 (at 5adff78bb168)

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

Repro files

About the diagrams

The diagram illustrates the repro's call against main 5adff78bb168 (Field.java:608-624, IndexingChain.java:1491-1510) and the 9.5.0 stored-fields path.

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.