MemoryIndex is Lucene's one-document, in-memory index. Its vector code has one branch per encoding, but the check on its first line still lets only the two older vector field classes through.
MemoryIndexholds a single document in memory. You add fields withaddField(or build it withfromDocument) and search it like a one-document index;lucene/monitoruses it to match one document against stored queries.- Vector fields come in one class per encoding:
KnnFloatVectorField(float32),KnnByteVectorField(byte) and, on main since #16383,KnnFloat16VectorField(float16, values asshort[]). Each extendsFielddirectly; none is a subclass of another. - MemoryIndex keeps one vector per field and returns it through
getFloatVectorValues,getByteVectorValuesorgetFloat16VectorValueson its reader. It runs no kNN search for any encoding.
storeVectorValues accepts only float32 and byte vector fields and throws before the FLOAT16 branch; with assertions off, the branch stores the vector and it reads back exactly. Select the still to open it at full size. Generated with AI, checked against the code.- Run a test with assertions on (Lucene's gradle build does for most seeds). Add one 1-dimensional float16 vector to an empty MemoryIndex:
addField(new KnnFloat16VectorField("v", new short[] {Float.floatToFloat16(1f)}, EUCLIDEAN), null). - You expect it to be stored like a float32 or byte vector:
getFloat16VectorValues("v").vectorValue(0)should return an equal 1-element vector, 1.0 in half precision. MemoryIndex has code for exactly this since float16 vectors were added. - Instead
addFieldthrowsjava.lang.AssertionError, with no message, atMemoryIndex.java:794, and no vector is stored (the field itself is already registered, with no values).MemoryIndex.fromDocumentandMonitor.matchon a single document fail the same way. - Why:
storeVectorValuesstarts withassert vectorField instanceof KnnFloatVectorField || vectorField instanceof KnnByteVectorField;, written in 2024 when those were the only two vector field classes.KnnFloat16VectorFieldis neither, so the FLOAT16 branch right below is never reached. - With assertions off, the JVM default, the same test passes and the vector reads back exactly. Adding
|| vectorField instanceof KnnFloat16VectorFieldto the assertion fixes it.
Anyone who puts a KnnFloat16VectorField into a MemoryIndex with assertions on: direct addField or fromDocument, or Monitor.match on one document. In practice that means test runs, Lucene's own and applications'. With assertions off, the JVM default, it works. Only unreleased main and 11.0 have the float16 field.
Mechanism, terms and numbers535 words
MemoryIndex.addField(IndexableField, Analyzer) stores a vector field through the private storeVectorValues (MemoryIndex.java:677-679, :793), whose first statement is assert vectorField instanceof KnnFloatVectorField || vectorField instanceof KnnByteVectorField; (:794). #13633 wrote it in 2024 (first released in 9.12.0), when those were the only vector field classes, and it was correct then. #16383 (2026-07-27, unreleased 11.0.0) added KnnFloat16VectorField, which extends Field and is not a KnnFloatVectorField, together with a case FLOAT16 arm in the same method (:819-840), the MemoryIndex reader's getFloat16VectorValues (:1782-1789) and MemoryFloat16VectorValues (:2425), but did not widen the assertion and added no MemoryIndex test. So with -ea, adding any float16 vector field to a MemoryIndex (directly, through fromDocument, or through Monitor.match on one document) throws a message-less AssertionError before the FLOAT16 arm runs. With -da the arm stores the vector and it reads back exactly. Only test runs are affected, and of the upstream refs inspected only main and branch_11x have the code. The fix is one line.
storeVectorValues(Info, IndexableField)(MemoryIndex.java:793-865): the private methodaddFieldcalls when the field type hasvectorDimension() > 0(:677-679). It switches on the field'sVectorEncoding(:795), with one arm per encoding: BYTE (:796-817), FLOAT16 (:819-840) and FLOAT32 (:842-863). Each arm checks the field's class, allows one value per field, and copies the vector.KnnFloat16VectorField(lucene/core/.../document/KnnFloat16VectorField.java):public class KnnFloat16VectorField extends Field(:37,@lucene.experimentalat:35), whose field type hasVectorEncoding.FLOAT16(:55).
addField(:620) creates the field'sFieldInfofrom the field type (getInfoat:622,createFieldInfoat:759-783), so the encoding isFLOAT16, and callsstoreVectorValues(:678). EveryfromDocumentoverload loops over the document's fields callingaddField(:571-573).- The method's first statement is
assert vectorField instanceof KnnFloatVectorField || vectorField instanceof KnnByteVectorField;(:794). For aKnnFloat16VectorFieldboth are false, so with-eait throwsjava.lang.AssertionError(no message) before any vector value is stored. The field is already registered by then:getInfoput itsInfoandFieldInfointo the field map (:740-744), so a caller that catches the error is left with a field thatFieldExistsQuerymatches andgetFloat16VectorValuesreturnsnullfor (executed probe). - With
-dathe assertion is skipped: the FLOAT16 arm checksinstanceof KnnFloat16VectorField, copies theshort[]into the field's float16 state (:1273,:1279), and the reader returns it throughgetFloat16VectorValues(:1782-1789) andMemoryFloat16VectorValues(:2425). - The assertion adds no check the arms lack for the vector field classes: each arm already throws
IllegalArgumentExceptionfor a field of the wrong class (:813-816,:836-839,:859-862). Its only other effect, with-ea, is that a customIndexableFieldwith vector attributes that is none of the listed classes fails there withAssertionErrorinstead of the arm'sIllegalArgumentException(read).
One field, one dimension, value Float.floatToFloat16(1f): with -ea, 1 test run, 1 failure at MemoryIndex.java:794 (gradle and standalone, seeds DEADBEEF and 5EED1234); with -da, 1 run, 0 failures and an exact read-back. Nothing depends on data, size, codec or threads.
Exposure and likelihood299 words
- Who is hit. Code that adds a
KnnFloat16VectorFieldto a MemoryIndex on a JVM with-ea:MemoryIndex.addField, everyMemoryIndex.fromDocumentoverload, andlucene/monitor'sMonitor.match(Document, ...)for a single document, which builds aSingletonDocumentBatchthat callsaddFieldfor every field (DocumentBatch.java:107-113). A public-API probe executed for this doc: with-ea,Monitor.matchthrowsAssertionErrorfromMemoryIndex.java:794viaDocumentBatch$SingletonDocumentBatch.<init>(DocumentBatch.java:110)andDocumentBatch.of(DocumentBatch.java:59); with-dathe registered query matches (1 match). Multi-document Monitor batches go throughIndexWriterand are not affected (read). The highlighters feed MemoryIndex token streams, not fields, so they are not affected (read). - When. Test runs. Lucene's gradle build enables assertions for about 75% of project seeds by default (
TestsAndRandomizationPlugin.java:240-248), andLuceneTestCaserequires-eato match-Dtests.asserts(TestRuleAssertionsRequired.java:36-54). Any application test suite run with-easees it too: a test that adds a valid float16 field through one of these paths fails. - Production is fine. With
-dathe vector is stored and read back exactly at every affected commit tested (bfe5c847d3c,ea57360912ed,645d4fe14a20). A service running production JVMs with-ea(unusual) would lose one operation per document with the field;AssertionErroris anError, socatch (Exception e)does not catch it (executed probe). - Why Lucene's tests miss it.
lucene/memory/src/testhas no float16 test, and no test elsewhere puts a float16 field into a MemoryIndex (read, grep). - Scale. Deterministic: every float16 vector field that can be constructed (any dimension and similarity; the constructor itself rejects a null or empty vector, a null similarity and a zero vector with COSINE,
KnnFloat16VectorField.java:39-53), on the first call. Unreleased: main andbranch_11xonly, of the refs inspected;KnnFloat16VectorFieldis@lucene.experimental. No downstream product was tested; one downstream project is still discussing whether to expose float16 at all (elastic/elasticsearch#163176).
One JUnit test, one method: add a 1-dimensional KnnFloat16VectorField to an empty MemoryIndex and read it back. Public API only, no randomness, single thread. It fails with assertions on, passes with them off, and passes with either fix.
/*
* 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.index.memory;
import org.apache.lucene.document.KnnFloat16VectorField;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.VectorSimilarityFunction;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestMemoryIndexFloat16Vector extends LuceneTestCase {
public void testAddFloat16VectorField() throws Exception {
short[] vector = {Float.floatToFloat16(1f)};
MemoryIndex mi = new MemoryIndex();
// expected: the vector is stored and read back; actual (-ea): AssertionError in storeVectorValues
mi.addField(new KnnFloat16VectorField("v", vector, VectorSimilarityFunction.EUCLIDEAN), null);
LeafReader reader = (LeafReader) mi.createSearcher().getIndexReader();
assertArrayEquals(vector, reader.getFloat16VectorValues("v").vectorValue(0));
}
}
cp TestMemoryIndexFloat16Vector.java $LUCENE/lucene/memory/src/test/org/apache/lucene/index/memory/
cd $LUCENE && ./gradlew :lucene:memory:test --tests org.apache.lucene.index.memory.TestMemoryIndexFloat16Vector -Ptests.seed=DEADBEEF -Ptests.asserts=true
# with -Ptests.asserts=false the test passes: the bug needs assertions enabled
- Expected
getFloat16VectorValues("v").vectorValue(0)returns the 1-element vector that was added (1.0 in half precision).- Observed
- With
-ea:java.lang.AssertionError(no message) atMemoryIndex.storeVectorValues(MemoryIndex.java:794), called fromaddField; with-dathe test passes.
More files, environment, full output254 words
The test sits in org.apache.lucene.index.memory next to TestMemoryIndex; it uses only public API, so it needs no package-private access. The failure is Lucene's own message-less AssertionError, so the comment line on the failing call states expected versus actual. Pass -Ptests.asserts=true explicitly: without it the build picks assertions from the seed.
As the regression test: the same test, with its comment line wrapped (below), as the first float16 test in lucene/memory/src/test/org/apache/lucene/index/memory/; if the maintainers prefer, the same method can go into the existing TestMemoryIndex next to its float32 and byte vector tests (:769, :824). Its one comment line is longer than 100 columns, which google-java-format (:lucene:memory:check) flags; with the line wrapped the module's checks pass and the test behaves the same.
Not shown by this test, same root cause, executed elsewhere: MemoryIndex.fromDocument (the finder's second test) and Monitor.match on one document (a reach probe).
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); the same MemoryIndex.java is on upstream main and branch_11x; no release has the float16 API |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux, aarch64 |
cp TestMemoryIndexFloat16Vector.java $LUCENE/lucene/memory/src/test/org/apache/lucene/index/memory/
cd $LUCENE && ./gradlew :lucene:memory:test --tests org.apache.lucene.index.memory.TestMemoryIndexFloat16Vector -Ptests.seed=DEADBEEF -Ptests.asserts=true
# with -Ptests.asserts=false the test passes: the bug needs assertions enabled
# gradle, main 645d4fe14a20, -Ptests.seed=DEADBEEF -Ptests.asserts=true
TestMemoryIndexFloat16Vector > testAddFloat16VectorField FAILED
java.lang.AssertionError
at __randomizedtesting.SeedInfo.seed([DEADBEEF:C69D0D8C0A2877EA]:0)
at org.apache.lucene.index.memory.MemoryIndex.storeVectorValues(MemoryIndex.java:794)
at org.apache.lucene.index.memory.MemoryIndex.addField(MemoryIndex.java:678)
at org.apache.lucene.index.memory.TestMemoryIndexFloat16Vector.testAddFloat16VectorField(TestMemoryIndexFloat16Vector.java:30)
ERROR: 1 test has failed:
- org.apache.lucene.index.memory.TestMemoryIndexFloat16Vector.testAddFloat16VectorField (:lucene:memory)
# -Ptests.asserts=false: 1 test, 0 failures
# 10.5.2 and 10.6.0 release jars: does not compile (cannot find symbol: class KnnFloat16VectorField)- #16383 added float16 storage to MemoryIndex on purpose, including a FLOAT16 branch in this very method; the assertion above it makes that branch unreachable with
-ea. - The assertion dates from 2024, before float16 existed; no javadoc, note or test excludes float16.
IndexWriteraccepts the same field with assertions on.
The full argument211 words
- Deliberate support. #16383's
MemoryIndex.javadiff (+110/-0) adds theKnnFloat16VectorFieldimport, thecase FLOAT16arm with its own one-value and wrong-class checks, the float16Infostate,getFloat16VectorValuesandMemoryFloat16VectorValueswith a scorer. The unchanged assertion on the method's first line contradicts all of it. - The migration note asks for this update.
lucene/MIGRATE.md:629-638("IEEE FLOAT16 vector APIs (GITHUB#16383)"): "Custom readers and exhaustiveVectorEncodingswitches need aFLOAT16arm." This switch got its arm; its guard did not. - History. The assertion came with #13633 (
9e831ee8097, 2024-08-07, "Add float|byte vector support to memory index", first released in 9.12.0), correct when only two vector field classes existed. #16383 did not touch it, and none of its review discussion mentions MemoryIndex. The earlier, unmerged fp16 PR #15549 had the same omission. - No exclusion anywhere. The
addFieldjavadoc (MemoryIndex.java:613-619) names no unsupported field type; noCHANGES.txtorMIGRATE.mdentry excludes MemoryIndex; no test expects the error, andlucene/memory/src/testhas no float16 test.TestMemoryIndextests the float32 and byte vector fields (:769,:824and their round-trip helpers). - Elsewhere it works.
IndexWriterindexes the same float16 field with-ea(verifiers' controls). - Precedent. #15334 fixed an NPE in the MemoryIndex vector path as a bug although MemoryIndex has no kNN search (
lucene/CHANGES.txt:1493-1494, 10.4.0).
The devil's-advocate check244 words
The case for intended: (1) the failure is assertion-only, so production with -da is unaffected; (2) KnnFloat16VectorField is @lucene.experimental and float16 support is openly incomplete (tracker #16725); (3) MemoryIndex runs no kNN search for any encoding (searchNearestVectors are no-ops, MemoryIndex.java:1800-1811; TestMemoryIndex.java:819: "we don't really do knn search right now for MemoryIndex"), so its vector support is partial anyway; (4) the assertion could be read as a precondition that only the float32 and byte classes may reach storeVectorValues.
Why it fails: (a) the float32 and byte classes carry the same @lucene.experimental tag (KnnFloatVectorField.java:38, KnnByteVectorField.java:38) and are supported and tested in MemoryIndex; (b) if float16 were meant to be excluded, #16383 would not have written a FLOAT16 arm, state, reader and values class for MemoryIndex; (c) the missing kNN search applies to every encoding and concerns searching, not storing; (d) nothing supports the precondition reading: the assertion predates the class it rejects; (e) "assertion-only" lowers the severity, not the validity: the assertion itself is the defect, and it fires in the configuration Lucene's tests use. The cross-model reviews rated the intended argument weak; the devil's advocate and the adjudication rejected it. A later cross-model fact check of this doc rated the same points moderate as a case that the defect is low-value and not worth an issue of its own, and weak as a case that it was designed so: two different questions. Verdict: not intended; a low-severity oversight.
Recommended A. Widen the assertion to the float16 field class
Add || vectorField instanceof KnnFloat16VectorField to the assertion at the top of storeVectorValues (the class is already imported). Validated with gradle: the repro passes, and so do all 52 lucene/memory tests. Dropping the assertion also works, since each branch already rejects a wrong field class.
private void storeVectorValues(Info info, IndexableField vectorField) {
- assert vectorField instanceof KnnFloatVectorField || vectorField instanceof KnnByteVectorField;
+ assert vectorField instanceof KnnFloatVectorField
+ || vectorField instanceof KnnFloat16VectorField
+ || vectorField instanceof KnnByteVectorField;
switch (info.fieldInfo.getVectorEncoding()) {All options and trade-offs2 options ยท 276 words
Both options were run through gradle in a scratch worktree of 645d4fe14a20 (seed DEADBEEF). Either way, add the minimal test as the regression test.
MemoryIndex.java:794; KnnFloat16VectorField is already imported at :40.
private void storeVectorValues(Info info, IndexableField vectorField) {
- assert vectorField instanceof KnnFloatVectorField || vectorField instanceof KnnByteVectorField;
+ assert vectorField instanceof KnnFloatVectorField
+ || vectorField instanceof KnnFloat16VectorField
+ || vectorField instanceof KnnByteVectorField;
switch (info.fieldInfo.getVectorEncoding()) {Trade-offs
- The smallest change; keeps the original guard and its meaning ("one of the vector field classes").
- A future vector encoding could fall into the same trap.
What the checks validated
- The repro passes with assertions on and off (1/1 each); it fails on stock main with assertions on.
- All
lucene/memorytests with assertions on: 4 classes, 52 tests, 0 failures (TestMemoryIndex30,TestMemoryIndexAgainstDirectory16,TestSlicedIntBlockPool5, the repro 1). :lucene:memory:check -x test --continuewith the fix and the comment-wrapped test passes (google-java-format, ecj lint and forbidden-apis on main and test sources).
Remove line :794. Dropping it loses no check for the vector field classes: each arm already throws IllegalArgumentException for a field of the wrong class (:813-816, :836-839, :859-862).
Trade-offs
- The next encoding cannot be blocked by it, and a custom vector field that is none of the three classes gets the same
IllegalArgumentExceptionwith-eaas it already does with-da. - Removes a guard some reviewers may want to keep.
What the checks validated
- The repro and
TestMemoryIndexwith assertions on: 2 classes, 31 tests, 0 failures.
Applications that feed documents with float16 vector fields to MemoryIndex or Monitor see this only in assertion-enabled runs, typically their test suites; production JVMs without -ea are unaffected.
Non-formal: a differential edge probe. The hunt on float16 vectors ran the same edge cases through float16 and float32 vector fields across Lucene's APIs; the MemoryIndex case failed only with assertions on, and the failing frame pointed at the assertion. Independent reproducers, an adjudication and reviewers from another model family then confirmed it.
The method in full131 words
Method: non-formal (a differential JUnit, not a model or a bounded-exhaustive check). The hunt unit on float16 vectors wrote edge probes that run a float16 field and its float32 twin through the same APIs and compare the outcomes. TestFp16Probes#testMemoryIndex failed with -ea and passed with -da; reading the failing frame (MemoryIndex.java:794) gave the root cause.
The finder wrote a two-test repro (addField, fromDocument): 2/2 fail with -ea, 2/2 pass with -da and with the one-line fix. It was then reviewed by a reviewer from another model family (OpenAI GPT-5.6, refute and works-as-designed lenses), reproduced by independent verifiers (one from a package outside Lucene, with float32 and byte controls), judged, traced through history, cut to the one-test repro on 645d4fe14a20, and reviewed again there.
No matching prior report found in the recorded searches of GitHub issues, PRs and commits, the JIRA archive, the Lucene mailing lists, downstream repositories or the web (searched 2026-10-26 and again 2026-11-01). Not fixed upstream, and no open PR touches the assertion.
Searches4 searches
Closest items, none a report of this bug: #16383 (introduced it), #13633 (wrote the assertion), #15549 (earlier unmerged fp16 PR with the same omission), #16725 (fp16 completion tracker, no MemoryIndex item), #15334 (a different MemoryIndex vector-path bug, an NPE, fixed as a bug), #16847 (open draft, renames in MemoryIndex only). Downstream: elastic/elasticsearch#163176 (whether to expose float16 at all) and opensearch-project/k-NN#3556 (its own percolator code). Limits: on the 2026-11-01 refresh, 18 planned queries were run by hand; GitHub search does not index review comments (those were read directly for the PRs named); no hit is not proof that nobody has noticed it.
| Where | When | How | Found |
|---|---|---|---|
| apache/lucene issues, PRs and commits; downstream repositories | 2026-10-26 and 2026-11-01 | scripted prior-report searches plus manual GitHub searches: MemoryIndex with float16 / fp16 / half float, KnnFloat16VectorField, Float16VectorValues, getFloat16VectorValues, MemoryFloat16VectorValues, storeVectorValues, the assertion text, MemoryIndex AssertionError, monitor and percolator with float16; elasticsearch, OpenSearch, k-NN and solr | no report |
| Pull requests by touched file | 2026-11-01 | file lists of all 409 open PRs, and of the closed PRs since 2026-07-01 (2026-10-26) and since 2026-10-20 (2026-11-01) | 7 open PRs touch MemoryIndex.java (#16847, #14716, #14475, #13948, #13525, #13342, #486); none touches storeVectorValues, the assertion or float16 |
| Review threads and recent activity | 2026-11-01 | #16383, #15549, #13633, #15334, #16725 and others read directly; all issues, PRs, conversation and review comments since 2026-10-22 | no mention of MemoryIndex with float16 |
| Mailing lists, JIRA archive, web | 2026-10-26 and 2026-11-01 | dev@, java-user@, issues@ and commits@ through the list archive API; JIRA-archive and web queries | only fp16 feature threads and commit notifications; float16 postdates JIRA |
Introduced on main by #16383 (bfe5c847d3c, 2026-07-27), which added float16 vectors and MemoryIndex's FLOAT16 branch but kept the 2024 assertion from #13633. No release has it; main and branch_11x (11.0.0) do. Executed at the introducing commit, its parent and current main.
Versions tested6 rows
Introduced by #16383 (PR title "Add fp16 (float16) vector encoding support - no quantization support"; merge commit subject "Add fp16 (float16) vector encoding (without support for quantization)"; Pulkit Gupta, merged 2026-07-27 as bfe5c847d3c; parent 606a26dafd5). The assertion line comes from #13633 (9e831ee8097, 2024-08-07; backport 38201baaf3a, first released in 9.12.0) and is correct in every release from 9.12.0 through 10.6.0, where only the BYTE and FLOAT32 arms exist. Found with git show, git blame and git log -S on the assertion text and on KnnFloat16VectorField over lucene/memory. branch_11x, cut on 2026-10-29 for 11.0.0, has the same MemoryIndex.java, so 11.0.0 would ship the bug unless it is fixed first.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | minimal repro fails with assertions on and passes with them off (gradle and standalone); passes with fix A or B |
main bfe5c847d3c (#16383), ea57360912ed | introduced | fails with -ea, passes with -da; at bfe5c847d3c the one-line fix makes it pass; a float32/byte control passes |
main a9779ef3c193 | -ea only | the finder's two-test class fails with -ea (2 of 2); only -ea was run there; MemoryIndex.java is the same blob (efd49fdc21fb) as at bfe5c847d3c and 645d4fe14a20 |
main 606a26dafd5 (parent of #16383) | no API | the test does not compile (no float16 API); the float32/byte control passes |
| 10.6.0, 10.5.2 (and every earlier release) | no API | the test does not compile (no KnnFloat16VectorField) and the float32/byte control passes, executed on 10.5.2 and 10.6.0; no release tag has the float16 API (read) |
upstream main, branch_11x | read | the same MemoryIndex.java (2026-11-01); the file was last changed by #16383, while line 794 itself still comes from #13633 (git blame) |
Confirmed with high confidence: the finder, independent reproducers, an intended-behaviour devil's advocate, an adjudication and three review sessions by a reviewer from another model family (OpenAI GPT-5.6) all reproduced it and agree on the root cause and the one-line fix.
Checks and verdicts393 words
Everyone agrees the impact is small: assertion-enabled runs only, an experimental field type, unreleased.
| Check | Verdict | What it found |
|---|---|---|
| finder | candidate | Differential edge probe failed with -ea, passed with -da; its two tests (addField, fromDocument) fail 2/2 with -ea and pass with -da and with the fix. |
| cross-model review, refute lens (OpenAI GPT-5.6) | real, high | Own repro fails with -ea, passes with -da and with the fix; float32 control passes; patched TestMemoryIndex 30/30. Intended argument weak. |
| independent reproducer | real | Own public-API test outside org.apache.lucene: -ea 4 run / 2 failed in the same frame with float32 and byte controls passing; -da 4/4 with an exact round trip; fix 4/4; 10.5.2 does not compile. |
| cross-model review, works-as-designed lens (OpenAI GPT-5.6) | real, high | Own repro -ea 4/2, -da 4/0; an IndexWriter float16 control passes with -ea; the fix passes its tests and TestMemoryIndex 30/30. Intended argument weak. |
| intended-behaviour devil's advocate | real, low severity | Built the strongest intended case and rejected it; own test -ea 4/2, -da 4/0; found the Monitor reach by reading the code. |
| adjudication | confirmed, high | Re-ran the independent reproduction's test with a new seed: -ea 4/2 at MemoryIndex.java:794, -da 4/0. |
| verify on 645d4fe14a20 and refute review (OpenAI GPT-5.6) | real; real, high | The minimal test fails with -ea, passes with -da and with the fix; the reviewer's own runs, on ea57360912ed: its 3-dimensional test with a float32 control fails with -ea and passes with the fix, and the full TestMemoryIndex passes with the fix; its rerun on 645d4fe14a20: -ea 2 run / 1 failed, -da 1/0, fix 2/0. |
| this doc | fails through gradle; fixes validated; Monitor reach executed | Gradle repro with assertions on and off, fixes A and B with the module's tests, and a Monitor.match probe (fails with -ea, matches with -da). |
| fact checks of this doc (Claude; OpenAI GPT-5.6) | real; errors fixed | Both re-checked the claims against the code, the history and the recorded runs; the Claude check re-ran the minimal test (-ea fails at :794, -da passes, fix A passes both) and the Monitor.match probe. One overstated run record, six factual errors (none high severity), four overclaims and three missing caveats were found; all are corrected in this version. |
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 429 KB
Finding record, Lucene and upstream sources, repro files and diagram notes872 words
- Registry title
- MemoryIndex.addField throws AssertionError under -ea for a KnnFloat16VectorField, because a stale assertion in storeVectorValues predates the FLOAT16 branch
- Area
- memory / MemoryIndex vectors
- Priority
- Low, rank 1166: MemoryIndex throws AssertionError under -ea for a KnnFloat16VectorField because of a stale assertion, test-suite only and unreleased.
- Impact class
- minor
- Impact note
- Only JVMs running with Java assertions enabled (-ea), such as Lucene's own tests and application test suites, that put a KnnFloat16VectorField into a MemoryIndex (addField, fromDocument, or lucene/monitor's single-document batch, executed by a Monitor.match probe); with -da the field is stored and read back correctly; unreleased main and branch_11x (11.0.0) only; ordinary scale.
- Summary
- You add a KnnFloat16VectorField to a MemoryIndex, through MemoryIndex.addField or MemoryIndex.fromDocument, with Java assertions enabled. You expect it to be stored and readable through getFloat16VectorValues, like a float32 or byte vector field, because MemoryIndex has a FLOAT16 storage branch and a FLOAT16 reader. Instead the first statement of storeVectorValues asserts that the field is a KnnFloatVectorField or a KnnByteVectorField, so the call throws AssertionError before the FLOAT16 branch runs; with assertions disabled the vector is stored and read back exactly.
- Introduced
- #16383 (bfe5c847d3c) [read: git blame, git log -S and -G over lucene/memory; exec: the repro fails at bfe5c847d3c under -ea, passes there under -da and with the one-line fix shadowed, and does not compile at its parent 606a26dafd5, which has no float16 API]; the assertion line itself was written by #13633 (9e831ee8097, 2024-08-07) and was correct then, when only the float32 and byte vector field classes existed (2026-07-27)
- First affected version
- unreleased (main and the 11.0 release branch branch_11x, 11.0.0-SNAPSHOT; no release tag contains bfe5c847d3c)
- Affected versions
- main from bfe5c847d3c (2026-07-27) through 645d4fe14a20 [exec: the repro fails under -ea and passes under -da at bfe5c847d3c, ea57360912ed and 645d4fe14a20; MemoryIndex.java is byte-identical at these commits]; branch_11x (cut 2026-10-29 for 11.0.0) has the same MemoryIndex.java [read 2026-11-01]; 10.6.0, 10.5.2 and 9.12.3 not affected (no float16 vector API, the repro does not compile) [exec]; 9.12.0, 9.12.3, 10.0.0 and 10.5.2 carry the same two-class assertion, which is correct there (a float32/byte control passes under -ea) [exec]; branch_10x and branch_10_6 have no KnnFloat16VectorField [read]
- 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
- non-formal
- Upstream and prior reports
- none found (2026-10-26, 208 read-only queries: 2 scripted prior-report searches with 54 GitHub issue/PR, 10 commit, 9 downstream, 40 mailing-list, 10 JIRA-archive and 9 web queries; 58 manual GitHub API searches; file lists of all 412 open PRs and of the 355 PRs created since 2026-07-01 and closed; 16 direct mailing-list queries; review threads of #16383, #15549, #13633, #16473, #16628, #16710, #16735, #16738, #16708 and tracker #16725 read directly); related prior items: #16383 (introduced it), #13633 (wrote the assertion), #15549 (earlier unmerged fp16 PR with the same omission), #16725 (fp16 tracker, no MemoryIndex item), #15334 (a MemoryIndex vector-path NPE fixed as a bug); open #16847 touches MemoryIndex.java but not storeVectorValues
- Lucene commit tested
- main
645d4fe14a20(gradle in a scratch worktree: the minimal test with assertions on and off, stock and with fix A, alllucene/memorytests with fix A, the memory module's static checks with fix A and the comment-wrapped test, the minimal test andTestMemoryIndexwith fix B; standalone runs with assertions on and off and a second seed; aMonitor.matchreach probe with assertions on and off), the 10.5.2 and 10.6.0 release jars (the test does not compile: no float16 API), and, by the history step and the hunt's verifiers, mainbfe5c847d3c(#16383), its parent606a26dafd5,ea57360912edanda9779ef3c193.MemoryIndex.javais the same blob atea57360912ed,645d4fe14a20, upstream main andbranch_11x(read, 2026-11-01).
- Finding record
lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:794 (the two-class assertion), :819-840 (the FLOAT16 arm), :564-575 (fromDocument calls addField per field), :620-679 (addField calls storeVectorValues at :678), :1783-1789 (getFloat16VectorValues), :2425 (MemoryFloat16VectorValues)lucene/core/src/java/org/apache/lucene/document/KnnFloat16VectorField.java:35-37 (@lucene.experimental; extends Field, not KnnFloatVectorField)lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java:107-113 (SingletonDocumentBatch calls MemoryIndex.addField for every field; first found by code reading, since executed by a Monitor.match probe on 645d4fe14a20: fails with -ea, matches with -da)lucene/CHANGES.txt:168 (GITHUB#16383 in the 11.0.0 section), :2574 (GITHUB#13633)lucene/memory/src/test (no float16 or fp16 reference: grep -rli 'float16\|fp16' finds no file)SECURITY.md:28 (downstream application sanitizes untrusted input), :50 (in-scope reports)
- The finding in 30 seconds
lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java at 645d4fe14a20 (:564-575, :620-679, :732-783, :793-865, :1273, :1279, :1782-1789, :1800-1811, :2425)lucene/core/src/java/org/apache/lucene/document/KnnFloat16VectorField.java at 645d4fe14a20 (:35, :37, :55)
- Who is affected
lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java at 645d4fe14a20 (:107-113); Monitor.java (:220, :240-242)build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/TestsAndRandomizationPlugin.java at 645d4fe14a20 (:240-248, :440-441)lucene/test-framework/src/java/org/apache/lucene/tests/util/TestRuleAssertionsRequired.java at 645d4fe14a20 (:36-54)
- Why it looks unintended
lucene/MIGRATE.md at 645d4fe14a20 (:629-638); lucene/CHANGES.txt (:174, :1493-1494, :2607)lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java at 645d4fe14a20 (:769, :819, :824)lucene/core/src/java/org/apache/lucene/document/KnnFloatVectorField.java (:38), KnnByteVectorField.java (:38)
- TestMemoryIndexFloat16Vector.java (JUnit, one test):
repro/LFV-090/src/org/apache/lucene/index/memory/TestMemoryIndexFloat16Vector.java(34 lines)
Made in parallel with this doc from the code at 645d4fe14a20 and the repro's input and output.
assets/LFV-090/bug.png(429 KB)
