CheckIndex's vectors test asks the reader for float32 or byte vectors and skips the field when it gets neither. The FLOAT16 encoding brought a third getter and a FLOAT16 checker, but not a third term in that skip test, so every FLOAT16 field is skipped.
- CheckIndex levels. Level 1, the command-line default, verifies file checksums only. Level 2 adds the logical integrity checks, such as reading every vector of a field and searching some of them; level 3 adds slow checks.
- One getter per encoding. A vector field has one encoding: FLOAT32 (
KnnFloatVectorField), BYTE (KnnByteVectorField) or, new in the unreleased 11.0, FLOAT16 (KnnFloat16VectorField). A segment reader has one getter per encoding, and each returns null for a field of another encoding.
- Index one document with a 2-dimension
KnnFloat16VectorField[1, 2](default codec), then run CheckIndex at level 2, the level that adds the logical integrity checks:setLevel(2)on the API or-level 2on the command line. - You expect the vectors test to count 1 field and 1 vector and to run the FLOAT16 checks. The same index built with
KnnFloatVectorFieldprintstest: vectors.............OK [1 fields, 1 vectors]. - Instead it prints
OK [0 fields, 0 vectors], in the same report wherefield infosandhnsw graphseach see the 1 field, and ends with "No problems were detected with this index." - Why: before checking a field, CheckIndex asks for float32 or byte vectors and skips the field when it gets neither. A FLOAT16 field returns null from both, so the FLOAT16 checker that came with the encoding never runs.
- Every FLOAT16 field is skipped every time, also in Lucene's own randomized tests that run CheckIndex when an index is closed. Unreleased 11.0 code (main and
branch_11x); indexing, search and file checksums are unaffected.
Anyone on main or branch_11x who indexes KnnFloat16VectorField and runs CheckIndex at level 2 or 3: the API, -level 2/3, TestUtil.checkIndex, the test framework's check-on-close. Every FLOAT16 field loses its vector checks and counts, so a FLOAT16 bug those checks catch (wrong dimension, missing or unsearchable vectors) passes unnoticed. Unreleased; search, data and checksums are unaffected.
Mechanism, terms and numbers606 words
On main, CheckIndex.testVectors skips a vector field when both getFloatVectorValues and getByteVectorValues return null (CheckIndex.java:2864-2867 at 645d4fe14a20). CodecReader's final getters return null for any field whose encoding is not FLOAT32 or BYTE respectively, so every FLOAT16 field (KnnFloat16VectorField, new in the unreleased 11.0 with #16383; on main and branch_11x) is skipped before it is counted: CheckIndex at level 2 or higher prints test: vectors.............OK [0 fields, 0 vectors] for an index with one FLOAT16 vector, while the same index with a FLOAT32 vector gives OK [1 fields, 1 vectors]. The FLOAT16 arm of the switch and the dedicated checkFloat16VectorValues (kNN self-search, dimension and size checks), added by the same commit, can never run. Lucene's randomized vector-format tests index FLOAT16 vectors and run CheckIndex on close, but a skipped field raises no error. File checksums and, on HNSW formats such as the default codec, the encoding-agnostic HNSW graph check still run, and indexing and search are not affected. The fix is one more term in the guard, the same widening #12064 made when byte vectors got their own getter.
checkIndexrunstestVectorsonly atlevel >= Level.MIN_LEVEL_FOR_INTEGRITY_CHECKS= 2 (CheckIndex.java:1114,:1141,:4754). The command-line default is level 1, "Checksum checks only" (:4748,:4837).- For each field with vector values,
testVectorsdoes:
if (reader.getFloatVectorValues(fieldInfo.name) == null
&& reader.getByteVectorValues(fieldInfo.name) == null) {
continue;
}
status.totalKnnVectorFields++;
switch (fieldInfo.getVectorEncoding()) {
case BYTE: checkByteVectorValues(...);
case FLOAT16: checkFloat16VectorValues(...);
case FLOAT32: checkFloatVectorValues(...);(CheckIndex.java:2864-2898, abridged; the FLOAT16 arm is :2878-2884.)
CodecReader.getFloatVectorValuesandgetByteVectorValuesarefinaland return null unless the field's encoding is FLOAT32 or BYTE respectively (CodecReader.java:235-252,:255-265). OnlygetFloat16VectorValuesreturns values for a FLOAT16 field (:269-279).- So for a FLOAT16 field the loop takes
continuebefore the counter (CheckIndex.java:2869) and before the switch.testVectorstakes aCodecReaderand the getters are final, socheckFloat16VectorValues(:3164-3213) cannot run for any reader. The summary line prints both counters:OK [0 fields, 0 vectors](:2902-2909).
- Still runs: file checksums (level 1 and up) and
testHnswGraphs(CheckIndex.java:2926-2945), which loops over every vector field whatever its encoding and checks the graph of each field whose reader is anHnswGraphProvider(:2937-2944), as with the default codec; the probe showshnsw graphs ... OK [1 fields]for the FLOAT16 index. - Lost for FLOAT16 fields: what
checkFloat16VectorValuesdoes, the same checks FLOAT32 and BYTE fields get: it reads every vector by ordinal and checks its length against the field's dimension; for each vector whose document id is a multiple ofmax(size/64, 1)(every vector, for fields with fewer than 128) it runs a k=10 kNN search that must return a hit (when the format supports search); and it checks that the number of vectors read equalssize(). Plus correct field and vector counts.
One document, one 2-dimension EUCLIDEAN vector [1, 2], default codec, CheckIndex <dir> -level 2 on the stock jar of 645d4fe14a20:
| field type | test: vectors | test: hnsw graphs |
|---|---|---|
KnnFloatVectorField | OK [1 fields, 1 vectors] | OK [1 fields] |
KnnFloat16VectorField | OK [0 fields, 0 vectors] | OK [1 fields] |
KnnFloat16VectorField, with the fix | OK [1 fields, 1 vectors] | OK [1 fields] |
The finder's test and the verifiers saw the same on larger indexes: a 2-field, 3-vector FLOAT16 index reports 0 fields / 0 vectors, and a segment with a FLOAT32 (7 vectors), a BYTE (5) and a FLOAT16 (11) field reports OK [2 fields, 12 vectors]: only the FLOAT16 field is missing.
Exposure and likelihood241 words
- Who is hit. Main and
branch_11x(FLOAT16 vectors are unreleased, 11.0.0). Default codec, any index size, any similarity. CheckIndex at level 2 or 3: the API withsetLevel, the command line with-level 2/3,TestUtil.checkIndex(Directory)(level 3,TestUtil.java:302-303) and the test framework's check-on-close (level 3, or 2 without slow checks;BaseDirectoryWrapper.java:32-33,:44-45,:64-70). The command-line default, level 1, checks only checksums for every encoding. - Deterministic. Every FLOAT16 field is skipped, every time: over 10 random-seed iterations every FLOAT16 test failed and every FLOAT32/BYTE control passed.
- CheckIndex's part of Lucene's test net. Lucene tests FLOAT16 reading, search and graphs directly (for example
TestHnswFloat16VectorGraph,TestFloat16VectorSimilarityQuery). What misses FLOAT16 is the CheckIndex layer:BaseKnnVectorsFormatTestCasedraws the vector encoding at random per test, FLOAT16 included (BaseKnnVectorsFormatTestCase.java:128,:147-157,:1351), and the indexes its tests make withnewDirectory()are checked on close. With the bug, a FLOAT16 defect that only CheckIndex would catch (wrong dimension, missing or unsearchable vectors) passes that check. FLOAT16 is new code still being completed (tracker #16725), which is where such checks matter most. - Default vs opt-in. The codec is the default; the experimental field type (
KnnFloat16VectorFieldis@lucene.experimental) and CheckIndex level 2 or higher are the opt-ins. - Not affected. Indexing, search hits, scores, live docs and stored data. File checksums and, on HNSW formats such as the default codec, the HNSW graph check still run for FLOAT16 fields.
One JUnit test on the public API: one document with one 2-dimension FLOAT16 vector, the default codec, CheckIndex at level 2, then assert that the vector status counts 1 field and 1 vector. Single thread, deterministic, any seed.
/*
* 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;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.KnnFloat16VectorField;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.tests.util.TestUtil;
public class TestCheckIndexFloat16Vectors extends LuceneTestCase {
public void testFloat16VectorFieldIsChecked() throws Exception {
try (Directory dir = new ByteBuffersDirectory()) {
IndexWriterConfig iwc = new IndexWriterConfig().setCodec(TestUtil.getDefaultCodec());
try (IndexWriter w = new IndexWriter(dir, iwc)) {
Document doc = new Document();
short[] v = {Float.floatToFloat16(1f), Float.floatToFloat16(2f)};
doc.add(new KnnFloat16VectorField("v", v, VectorSimilarityFunction.EUCLIDEAN));
w.addDocument(doc);
}
try (CheckIndex checker = new CheckIndex(dir)) {
checker.setLevel(CheckIndex.Level.MIN_LEVEL_FOR_INTEGRITY_CHECKS);
CheckIndex.Status.VectorValuesStatus s =
checker.checkIndex().segmentInfos.get(0).vectorValuesStatus;
// 1 FLOAT16 field with 1 vector was indexed; CheckIndex reports 0 fields, 0 vectors
assertEquals("vector fields checked", 1, s.totalKnnVectorFields);
assertEquals("vector values checked", 1, s.totalVectorValues);
}
}
}
}
cd $LUCENE && ./gradlew :lucene:core:test --tests TestCheckIndexFloat16Vectors -Ptests.seed=DEADBEEF
- Expected
totalKnnVectorFields= 1 andtotalVectorValues= 1: the one FLOAT16 field and its one vector are checked.- Observed
AssertionError: vector fields checked expected:<1> but was:<0>, with assertions on and off.
More files, environment, full output241 words
KnnFloat16VectorField, IndexWriter, CheckIndex.setLevel and CheckIndex.Status, plus Lucene's test framework (LuceneTestCase, TestUtil), so it lives in Lucene's test tree; no package-private members; the default codec is pinned with TestUtil.getDefaultCodec() because the test framework otherwise randomizes it; ByteBuffersDirectory. Copy the file into lucene/core/src/test/org/apache/lucene/index/ and run the command; the same result with -Ptests.asserts=false.
Regression test: this same test; it passes with the fix and fits as one more method in TestCheckIndex.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT): fails; with the fix: passes. Releases 10.6.0, 10.5.2, 9.12.3: no FLOAT16 API, the test does not compile |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux, aarch64 (the only platform run; the code path is pure Java) |
cp $REPRO/TestCheckIndexFloat16Vectors.java $LUCENE/lucene/core/src/test/org/apache/lucene/index/
cd $LUCENE && ./gradlew :lucene:core:test --tests TestCheckIndexFloat16Vectors -Ptests.seed=DEADBEEF
# the same result with -Ptests.asserts=false
# gradle, stock 645d4fe14a20, -Ptests.seed=DEADBEEF (assertions on; the same with -Ptests.asserts=false)
TestCheckIndexFloat16Vectors > testFloat16VectorFieldIsChecked FAILED
java.lang.AssertionError: vector fields checked expected:<1> but was:<0>
at __randomizedtesting.SeedInfo.seed([DEADBEEF:77A3C607A3646CF3]:0)
at org.junit.Assert.fail(Assert.java:89)
at org.junit.Assert.failNotEquals(Assert.java:835)
at org.junit.Assert.assertEquals(Assert.java:647)
at org.apache.lucene.index.TestCheckIndexFloat16Vectors.testFloat16VectorFieldIsChecked(TestCheckIndexFloat16Vectors.java:42)
# with the recommended fix: 1 test, 0 failures (assertions on and off)
# the same one-vector index through the command line, CheckIndex <dir> -level 2, stock jar:
test: field infos.........OK [1 fields] [took 0.000 sec]
test: vectors.............OK [0 fields, 0 vectors] [took 0.000 sec]
test: hnsw graphs.........OK [1 fields] [took 0.002 sec]
No problems were detected with this index.
# with the fix: test: vectors.............OK [1 fields, 1 vectors]- The same commit (#16383) added a FLOAT16 arm and a dedicated
checkFloat16VectorValues; the guard in front of them makes both unreachable. - When byte vectors got their own getter (#12064), this same guard was widened for it in the same change.
totalKnnVectorFieldsis documented as "Total number of fields with vectors"; a field with a FLOAT16 vector reports 0.
The full argument240 words
- A checker that cannot run.
git log -S 'checkFloat16VectorValues'andgit log -S 'case FLOAT16:'onCheckIndex.javareturn only #16383 (bfe5c847d3c). Nobody writes a checker in order never to call it. - Precedent in the same guard. #12064 (
cc29102a24a, 2023) gave byte vectors their own field and getter (KnnByteVectorField,getByteVectorValues; byte-encoded vectors themselves date from LUCENE-10577, released in 9.4.0), made the getters return null on an encoding mismatch, and widened this guard from one getter to two in the same change. - Contract text.
totalKnnVectorFields: "Total number of fields with vectors" (CheckIndex.java:420-421). Level 2: "All level 1 checks + logical integrity checks" (:4838).LeafReader.getFloat16VectorValuesreturns values "or null if no Float16VectorValues were indexed" (LeafReader.java:237-243), the same contract as the other getters. MIGRATE.md's #16383 section: "exhaustiveVectorEncodingswitches need aFLOAT16arm" (MIGRATE.md:629-645); CheckIndex's switch did get that arm and the omission is the null guard in front of it, so this supports "every encoding dispatch must handle FLOAT16" rather than naming this guard. - Tests.
testCheckIndexIncludesVectors(BaseKnnVectorsFormatTestCase.java:1947-1976: "Make sure CheckIndex in fact declares that it is testing vectors!") andTestCheckIndex.java:203-207assert the vector counts, forKnnFloatVectorFieldonly. No test, javadoc, CHANGES or MIGRATE entry says CheckIndex excludes FLOAT16. - History. None of the 101 texts of the #16383 thread mentions CheckIndex; the earlier, unmerged fp16 PR #15549 had the identical CheckIndex hunk without the guard term.
The devil's-advocate check169 words
The strongest case for "intended": FLOAT16 is unreleased and @lucene.experimental, and so is CheckIndex itself (CheckIndex.java:110); the fp16 work is openly incomplete (tracker #16725 says "there might be more" missing places); the guard is old and deliberate (it skips fields the codec has no values for); nothing is corrupted or mis-searched; and the call-site comment still reads "// Test FloatVectorValues and ByteVectorValues" (:1140).
Why it fails: every point is about impact or timing, not intent. The same commit wrote the FLOAT16 checker, so the skip is an omission. The guard's purpose, skipping fields with no values, is kept by the fix, since getFloat16VectorValues returns null exactly when no FLOAT16 values exist. The comment is pre-FLOAT16 text, not a design statement. The cross-model reviews rated the intended argument weak; a later cross-model fact check agreed, calling it moderate only as a triage argument (maintainers may fold it into the incomplete fp16 work of #16725 at low priority). Verdict: not intended, a defect of low severity.
Recommended Add the FLOAT16 getter to the guard
Add && reader.getFloat16VectorValues(fieldInfo.name) == null to the skip guard in CheckIndex.testVectors, the same widening #12064 made for BYTE. The repro passes with it, the FLOAT16 index then reports OK [1 fields, 1 vectors], and the closest suites, which check randomized FLOAT16 indexes on close, still pass.
if (reader.getFloatVectorValues(fieldInfo.name) == null
&& reader.getByteVectorValues(fieldInfo.name) == null
&& reader.getFloat16VectorValues(fieldInfo.name) == null) {
continue;
}All options and trade-offs2 options ยท 447 words
One option was built and validated; the alternative makes the compiler catch the next new encoding.
One term in CheckIndex.testVectors (CheckIndex.java:2864-2867), +2/-1. Optionally also update the stale call-site comment at :1140 ("Test FloatVectorValues and ByteVectorValues"); comment only, not part of the validated diff.
if (reader.getFloatVectorValues(fieldInfo.name) == null
&& reader.getByteVectorValues(fieldInfo.name) == null
&& reader.getFloat16VectorValues(fieldInfo.name) == null) {
continue;
}Trade-offs
- Cost. Not benchmarked. The re-enabled checker has the same shape as the FLOAT32 and BYTE ones (one pass over the vectors plus sampled searches), so a level-2 check of a FLOAT16 field is expected to cost about what a FLOAT32 field costs.
- Side effect, intended. The checker now runs in every test that closes a FLOAT16 index, so randomized runs could surface latent FLOAT16 codec bugs; the suites below found none.
What the checks validated
Applied to a scratch worktree of 645d4fe14a20 and run through gradle with OpenJDK 25.0.3, seed DEADBEEF (2026-10-31; an independent fact check re-ran the stock and fixed gradle runs, assertions on and off):
- stock: the minimal test fails with assertions on and off;
- with the fix: the minimal test passes with assertions on and off;
- the command-line probe prints
OK [1 fields, 1 vectors]for the one-vector FLOAT16 index (unchangedOK [1 fields, 1 vectors]for FLOAT32); - the closest suites pass:
TestCheckIndex(9 tests), every core vector-format suite (lucene99.*,lucene104.*,TestPerFieldKnnVectorsFormat) and the FLOAT16 query and HNSW graph tests (21 classes with the other tests the package globs picked up, such asTestForUtil: 509 tests, 0 failures);TestSimpleTextKnnVectorsFormatand the sandboxTestDedupHnswVectorsFormat, 57 tests each, 0 failures. These suites draw the encoding at random per test, FLOAT16 included, and check the indexes they make withnewDirectory()on close. Core lint (checkGoogleJavaFormat,ecjLintMain,forbiddenApisMain) passes; - earlier: the finder's sweep of 396 indexes (11 stock vector formats and encodings x 4 similarities x dimensions 3, 64, 1024 x 3 index shapes) was clean with the fix while the stock jar failed at the first index, and the verifiers' and the cross-model reviewers' own tests passed with it.
Fetch the values once with a switch expression on fieldInfo.getVectorEncoding() (BYTE -> getByteVectorValues, FLOAT16 -> getFloat16VectorValues, FLOAT32 -> getFloatVectorValues) and skip when the result is null.
Trade-offs
A switch expression over an enum must be exhaustive, so without a default arm javac forces whoever adds a fourth encoding to handle it, the omission that happened here (a classic switch statement would not). A larger diff with the same behaviour as the first option today.
Tools that run CheckIndex at level 2 or higher as an index health check on main get no vector checks for FLOAT16 fields until a fix ships.
While building an fp16-vs-fp32 differential harness, the finder listed every place Lucene dispatches on the vector encoding and checked each for a FLOAT16 arm. CheckIndex had one, behind a guard that tests only the float32 and byte getters; a FLOAT16 twin of the upstream test testCheckIndexIncludesVectors then failed on the stock main jars.
The method in full134 words
Method: non-formal, code reading followed by an executed test (hunt unit on fp16 vectors, 2026-10-25, main ea57360912ed).
- The finder enumerated every reader and codec dispatch site on
VectorEncodingwith grep and checked each for a FLOAT16 arm.CheckIndex.testVectorshas one, but the guard in front of it tests onlygetFloatVectorValuesandgetByteVectorValues. - It wrote the FLOAT16 twin of the upstream
BaseKnnVectorsFormatTestCase#testCheckIndexIncludesVectors(2 documents, 2 fields, 3 vectors) next to a FLOAT32 control. On the stock jar the FLOAT16 test failed withOK [0 fields, 0 vectors] expected:<3> but was:<0>, with assertions on and off; the control passed. - The one-term fix made it pass, and a 396-index sweep over the stock vector formats found no false positive with the fix.
- A later minimization reduced it to the one-document test shown here (2026-10-29).
No prior report found (2026-10-25 and 2026-10-26): GitHub issues, PRs and commits, the JIRA archive, four mailing lists, downstream projects, the web and the files of all 412 open PRs. Related: #16383 introduced the code; the open fp16 tracker #16725 does not list CheckIndex.
Searches2 searches
Related items, none of them this bug:
- #16383 introduced the code; #15549, the earlier unmerged fp16 PR, had the same omission.
- #16725, the open fp16 completion tracker, has no CheckIndex item: a natural place to link a report.
- #12064 widened the same guard for BYTE vectors; #11916 (the kNN self-search, float only) and #13984 (the encoding-agnostic HNSW graph check, which still runs for FLOAT16 on HNSW formats) are older CheckIndex vector work.
- Open draft #16847 edits lines inside
checkFloat16VectorValuesbut not the guard, so the method stays unreachable.
Searches are best effort: no hit does not prove that nobody has noticed it.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues, PRs and commits (apache/lucene incl. the migrated JIRA archive), downstream projects, mailing lists (dev@, java-user@, issues@, commits@), JIRA, web | 2026-10-25, 2026-10-26 | the scripted prior-report search, 59 queries (24 GitHub issue/PR, 3 commit, 2 downstream, 20 mailing-list, 4 JIRA-archive, 6 web; 604 distinct hits, the 33 medium-overlap hits read) | no report of this bug |
| GitHub (apache/lucene; 4 of the 30 on downstream projects), manual | 2026-10-26 | 30 issue and commit searches (for example CheckIndex float16, "checkFloat16VectorValues", "totalKnnVectorFields", "KnnFloat16VectorField"); the changed-file lists of all 412 open PRs; full reads of the #16383 and #15549 threads and of tracker #16725 | no report; no open PR changes the guard |
Introduced on main by #16383 (bfe5c847d3c, 2026-07-27), which added FLOAT16 vectors, the FLOAT16 arm and its checker, but not the guard term. Not a regression: the checks were unreachable from the start. No release has FLOAT16 vectors; upstream main and branch_11x still have the two-term guard.
Versions tested6 rows
Introduced by #16383 "Add fp16 (float16) vector encoding support - no quantization support" (bfe5c847d3c, merged 2026-07-27, listed under 11.0.0 in CHANGES.txt). The guard is older: c02f07f2d5d (LUCENE-9322, 2020) wrote if (values == null) continue;, cc29102a24a (#12064, 2023) widened it for BYTE, and 92e67ec626b (#12105, 2023) renamed the getter. git tag --contains bfe5c847d3c lists no release tag.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | minimal test fails, assertions on and off; passes with the fix; the command line prints OK [0 fields, 0 vectors] |
bfe5c847d3c (#16383), 091a987a14d, 55fd2e75fb2, 7cecf795236, ea57360912ed | affected | the finder's test fails the same way (assertions on and off at bfe5c847d3c and ea57360912ed); the one-term fix passes at bfe5c847d3c |
parent 606a26dafd5, 10.5.2, 9.12.3, 10.6.0 (RC1 and released jars, 4ae946e5a9b9) | not affected | no FLOAT16 API: the test does not compile |
branch_10x, the 10.6.0 tag | read | no FLOAT16 in VectorEncoding |
branch_11x 603af870375 (2026-10-29) | read | contains bfe5c847d3c; the same two-term guard (CheckIndex.java:2864-2866); #16383 listed under 11.0.0 |
upstream main 2f424fd428d (2026-10-30, now 12.0.0-SNAPSHOT) | read | the two-term guard is unchanged; no commit since 645d4fe14a20 touches CheckIndex.java |
Confirmed by an executed cross-model review (OpenAI GPT-5.6), an independent reproducer, a second cross-model review with a works-as-designed lens, a devil's-advocate verifier and an adjudication; the version-history runs, a minimization and a re-verification on 645d4fe14a20 re-ran it. No reviewer dissented. This doc's gradle run reproduced it and validated the fix; an independent and a cross-model fact check re-checked the doc.
Checks and verdicts368 words
Every check ran its own test on the stock main jars; the adjudication re-ran the independent reproduction's class with new seeds.
| Check | Verdict | What it found |
|---|---|---|
| Finder (grep inventory of every VectorEncoding dispatch site, then JUnit) | candidate | FLOAT16 twin of testCheckIndexIncludesVectors fails 1 of 2 on main with assertions on and off; the FLOAT32 control passes; the fix passes; 396-index sweep clean with the fix. |
| refute review (OpenAI GPT-5.6, executed) | real (high) | Own one-document test fails (on, off, 3 iterations), the FLOAT32 twin passes; the fix passes it and TestCheckIndex (9/0). Added that level 2 or higher is needed. |
| Independent reproducer | real | Own 6-test class: the 3 FLOAT16 tests fail, the 3 FLOAT32/BYTE controls pass (on, off; 30 of 60 over 10 seeds); a mixed segment reports 2 fields / 12 vectors; the fix passes 6/6. |
| Intended lens (OpenAI GPT-5.6, executed) | real (high) | Intended argument weak; own direct CheckIndex test fails 1 of 2 (on, off) and passes with the fix. |
| Intended-behaviour devil's advocate | real (low severity) | Own mixed-encoding test fails (on, off); the experimental/unreleased/no-data-impact case is about impact, not intent. |
| Adjudication | confirmed (high) | Re-ran the independent reproduction's class with new seeds: 3 of 6 fail (on, off), 6/6 pass with the fix; lowered the class to minor. |
Minimization and re-verification on 645d4fe14a20 | still reproduces | The one-test class fails (on, off; two seeds) and passes with the fix; the finder's, the independent reproduction's and the intended-behaviour check's tests still fail; the 10.6.0 RC1 jars do not compile the finder's test. |
| This doc (gradle, scratch worktree) | real; fix validated | Minimal test fails on stock and passes with the fix (on, off); command-line probe; closest suites pass with the fix. |
| Independent fact check of this doc | real; two history wordings corrected | Re-ran the minimal test (standalone runner and the gradle command, on and off) on stock and with the fix, the command-line probe, and the released 10.6.0 jars (the test does not compile). |
| Cross-model fact check (OpenAI GPT-5.6, read-only) | real; intended argument weak | Reviewed the brief against the code and the run logs; found the branch_11x scope and checker-description wordings, corrected in this doc. |
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 477 KB
Finding record, Lucene and upstream sources, repro files and diagram notes997 words
- Registry title
- CheckIndex silently skips FLOAT16 vector fields: it reports 'OK [0 fields, 0 vectors]' and never runs checkFloat16VectorValues
- Area
- index / CheckIndex vectors
- Priority
- Low, rank 750: CheckIndex reports 'OK [0 fields]' and never runs the FLOAT16 checks, including in the test framework's check-on-close, which hides bugs in a new feature, but data is unaffected and it is unreleased with a one-term fix.
- Impact class
- minor
- Impact note
- Anyone who runs CheckIndex at level 2 or higher (the CheckIndex API with setLevel, the CLI with -level 2/3, TestUtil.checkIndex and the test framework's check-on-close) over an index with a KnnFloat16VectorField on unreleased main (11.0.0), default codec, any size; indexing and search are unaffected and file checksums and the HNSW graph check still run, so what is lost is the logical FLOAT16 vector checks and correct field/vector counts.
- Summary
- You index vectors with KnnFloat16VectorField (default codec) and run CheckIndex at level 2 or higher. You expect it to count and check the FLOAT16 fields the way it does FLOAT32 and BYTE fields; the same 2-field, 3-vector index built with KnnFloatVectorField reports 2 fields / 3 vectors. Instead it prints 'test: vectors.............OK [0 fields, 0 vectors]', because the skip guard in CheckIndex.testVectors tests only the FLOAT32 and BYTE getters, so every FLOAT16 field is skipped and the dedicated checkFloat16VectorValues (iteration, dimension, size and kNN self-search checks) never runs.
- Introduced
- bfe5c847d3c (#16383 'Add fp16 (float16) vector encoding') [read: git blame, and git log -S 'checkFloat16VectorValues' / -S 'case FLOAT16:' on CheckIndex.java return only this commit; exec: the repro fails at bfe5c847d3c with -ea and -da and passes there with the one-term guard fix, and its parent 606a26dafd5 has no FLOAT16 API]; the guard itself is older (c02f07f2d5d LUCENE-9322, widened for BYTE in cc29102a24a #12064, renamed in 92e67ec626b #12105); not a regression: the FLOAT16 arm and checker were unreachable from the commit that added them (2026-07-27)
- First affected version
- unreleased (main only, 11.0.0-SNAPSHOT; no release tag contains bfe5c847d3c)
- Affected versions
- main from bfe5c847d3c (2026-07-27) through 645d4fe14a2 [exec: bfe5c847d3c, 091a987a14d, 55fd2e75fb2, 7cecf795236, ea57360912ed and 645d4fe14a2 fail; -ea and -da at bfe5c847d3c, ea57360912ed and 645d4fe14a2]; 10.5.2, 9.12.3 and the parent 606a26dafd5 not affected (no FLOAT16 API, the repro does not compile) [exec]; branch_10x 657014b3c579 (after #16905) and branch_10_6 4ae946e5a9b (10.6.0 RC1) have no FLOAT16 encoding [read; the repro does not compile on the 4ae946e5a9b jars, exec]; not fixed upstream as of main 645d4fe14a2 [exec 2026-10-29]
- 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: code reading
- Upstream and prior reports
- none found (2026-10-25 and 2026-10-26: a scripted prior-report search with 24 GitHub issue/PR, 3 commit, 2 downstream, 20 mailing-list (dev@, java-user@, issues@, commits@), 4 JIRA-archive and 6 web queries; 30 manual read-only gh issue/commit searches; changed-file lists of all 412 open PRs; full reads of the #16383 and #15549 threads and of the open fp16 tracker #16725); related prior items: #16383 (introduced the code), #15549 (earlier unmerged fp16 PR with the same omission), #16725 (open fp16 completion tracker, does not list CheckIndex), #12064 (widened the same guard for BYTE), #13984 and #11916 (older float-only CheckIndex vector checks); open draft #16847 edits lines inside checkFloat16VectorValues but not the guard
- Lucene commit tested
- main
645d4fe14a20(2026-10-28): the minimal test fails with assertions on and off, through gradle in a scratch worktree and through a standalone runner (two seeds); with the one-term fix it passes, and so do the closest suites (TestCheckIndex, every core vector-format suite and the FLOAT16 query and graph tests: 21 classes with the other tests the package globs picked up, 509 tests, 0 failures; the SimpleText and dedup vector formats: 57 + 57 tests, 0 failures) and core lint. A command-line probe on the stock jar printsOK [0 fields, 0 vectors]for a one-vector FLOAT16 index andOK [1 fields, 1 vectors]with the fix. History:bfe5c847d3c(#16383) and every later commit run fail; 10.5.2, 9.12.3 and 10.6.0 (the RC1 and the released jars) cannot compile the test (no FLOAT16 API). Upstream main2f424fd428dandbranch_11x603af870375still have the two-term guard (read, 2026-10-31).
- Finding record
lucene/core/src/java/org/apache/lucene/index/CheckIndex.java:417-421 (VectorValuesStatus javadoc: 'Total number of vector values tested', 'Total number of fields with vectors'), :1114 (testVectors runs only at level >= MIN_LEVEL_FOR_INTEGRITY_CHECKS), :2864-2867 (skip guard, FLOAT32 and BYTE getters only), :2878-2884 (FLOAT16 arm), :3164-3213 (checkFloat16VectorValues), :4673 and :4748-4754 (CLI default level 1; MIN_LEVEL_FOR_INTEGRITY_CHECKS = 2)lucene/core/src/java/org/apache/lucene/index/CodecReader.java:233-271 (final getters return null unless the field's encoding matches)lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseKnnVectorsFormatTestCase.java:1947-1976 (testCheckIndexIncludesVectors, FLOAT32 only); lucene/core/src/test/org/apache/lucene/index/TestCheckIndex.java:203-207 (vector counts asserted for FLOAT32 only)lucene/test-framework/src/java/org/apache/lucene/tests/util/TestUtil.java:302-303 (checkIndex(Directory) uses level 3); lucene/test-framework/src/java/org/apache/lucene/tests/store/BaseDirectoryWrapper.java:32-33, 44-45 (check-on-close)lucene/MIGRATE.md:629-645 ('IEEE FLOAT16 vector APIs (GITHUB#16383)': VectorEncoding switches need a FLOAT16 arm)lucene/core/src/java/org/apache/lucene/index/CodecReader.java:234-280 (vector getters on main 645d4fe14a2)
- The finding in 30 seconds
lucene/core/src/java/org/apache/lucene/index/CheckIndex.java:417-421, (at 645d4fe14a20) :1114, :1140-1144, :2843, :2864-2898, :2902-2909, :2926-2945, :3164-3213, :4748, :4754, :4837-4838lucene/core/src/java/org/apache/lucene/index/CodecReader.java:234-280 (at 645d4fe14a20)
- Who is affected
lucene/test-framework/src/java/org/apache/lucene/tests/util/TestUtil.java:302-303; (at 645d4fe14a20) lucene/test-framework/src/java/org/apache/lucene/tests/store/BaseDirectoryWrapper.java:32-33, :44-45, :64-70; lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseKnnVectorsFormatTestCase.java:124-130, :147-157, :1351-1353; lucene/core/src/java/org/apache/lucene/document/KnnFloat16VectorField.java:25-37
- Why it looks unintended
lucene/core/src/java/org/apache/lucene/index/CheckIndex.java:103-112, (at 645d4fe14a20) :417-421, :1140, :4836-4839; lucene/core/src/java/org/apache/lucene/index/LeafReader.java:237-243; lucene/MIGRATE.md:629-645lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseKnnVectorsFormatTestCase.java:1947-1976; (at 645d4fe14a20) lucene/core/src/test/org/apache/lucene/index/TestCheckIndex.java:104-105, :203-207
- Suggested fix
lucene/core/src/java/org/apache/lucene/index/CheckIndex.java:1140, (at 645d4fe14a20) :2864-2867, :3112-3264
- History
a Lucene clone: git show releases/lucene/10.6.0:lucene/core/src/java/org/apache/lucene/index/VectorEncoding.java; git show origin/main (2f424fd428d):lucene/core/src/java/org/apache/lucene/index/CheckIndex.java:2864-2866; git log 645d4fe14a20..origin/main -- CheckIndex.java (empty) (read, 2026-10-31)lucene/CHANGES.txt:174 (at 645d4fe14a20)a Lucene clone: git merge-base --is-ancestor bfe5c847d3c origin/branch_11x (603af870375); git show origin/branch_11x:lucene/core/src/java/org/apache/lucene/index/CheckIndex.java:2864-2866; CHANGES.txt headers on origin/main (12.0.0) and origin/branch_11x (11.0.0) (read, 2026-10-31)
- TestCheckIndexFloat16Vectors.java (one test):
repro/LFV-089/src/org/apache/lucene/index/TestCheckIndexFloat16Vectors.java(47 lines)
Made in parallel with this doc from the code at 645d4fe14a20, the minimal repro and the command-line probe of this brief.
assets/LFV-089/bug.png(477 KB)
![Diagram of the bug: an index with one KnnFloat16VectorField is checked with CheckIndex at level 2. In testVectors the skip guard asks getFloatVectorValues and getByteVectorValues; both return null for a FLOAT16 field, so the field is skipped before the FLOAT16 checker. The report says test: vectors OK [0 fields, 0 vectors], while the same index with a FLOAT32 vector says OK [1 fields, 1 vectors].](../assets/LFV-089/bug.png)