All findings · Lucene finding · LFV-092 · constknnfloat16valuesource-accepts-non-finite

ConstKnnFloat16ValueSource accepts NaN and infinite float16 query vectors that its float32 twin rejects

ConstKnnFloat16ValueSource accepts a float16 query vector with a NaN or infinite component, which its float32 twin ConstKnnFloatValueSource and the float16 kNN query reject with IllegalArgumentException. The bad vector surfaces only while scoring: an AssertionError inside VectorUtil with assertions on (COSINE scores 0 instead), scores of 0 or +Infinity with them off.

Summary

Priority
Low, rank 1167ConstKnnFloat16ValueSource accepts NaN/Inf query halves that its float32 twin rejects, a benchmark-helper class in unreleased code with no index damage.
Impact
wrong error
Since
main only: #16383 (bfe5c847d3c, 2026-07-27)introduced 2026-07-27
Affected
unreleased main and branch_11x; no release
Status
documented
Found by
non-formal: a float16-vs-float32 twin diff

AI-assisted page: this finding was first signalled by code reading or testing 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

Background: float16 vectors and constant-vector function queries

  1. A float16 (half-precision) value is stored in a short. It has 5 exponent bits; when all 5 are set the value is +Infinity, -Infinity or NaN. The largest finite half is 65504.
  2. Lucene's float16 kNN field and queries reject a vector with such a component (VectorUtil.checkFiniteFloat16), as the float32 field and query have since 9.7.0 (VectorUtil.checkFinite).
  3. For exact scoring with a function query, ConstKnnFloat16ValueSource supplies the query vector for every document and Float16KnnVectorFieldSource each document's own vector; Float16VectorSimilarityFunction compares the two inside a FunctionQuery. ConstKnnFloatValueSource is the float32 twin.
Diagram of the bug. A float16 query vector with a NaN or infinite component is passed to new ConstKnnFloat16ValueSource, which only null-checks it and accepts it, while new ConstKnnFloatValueSource rejects the same kind of vector with IllegalArgumentException through VectorUtil.checkFinite. During scoring, Float16VectorSimilarityFunction compares the vector with each document's vector: with assertions on VectorUtil throws an AssertionError, with assertions off FunctionQuery returns 0 for NaN, or +Infinity for DOT_PRODUCT with an infinite component. Expected: IllegalArgumentException at construction.
The float32 and float16 constant-vector sources side by side: the float32 one rejects a NaN or infinite component when it is built, the float16 one accepts it, and the bad value only shows up later, while documents are scored. Select the still to open it at full size. Generated with AI, checked against the code.

The finding, step by step

  1. Turn a float32 query into float16 with Float.floatToFloat16. The largest finite half is 65504, so a component of 65520 becomes +Infinity (0x7C00), and NaN stays NaN (0x7E00). Pass it to new ConstKnnFloat16ValueSource(q) to score KnnFloat16VectorField documents with a FunctionQuery over Float16VectorSimilarityFunction.
  2. You expect IllegalArgumentException right there: new ConstKnnFloatValueSource(new float[] {Float.NaN}) throws it, and so do KnnFloat16VectorQuery and KnnFloat16VectorField for the same float16 vector.
  3. Instead the constructor returns normally, with assertions on and off. It only null-checks the array; the float32 twin wraps the same argument in VectorUtil.checkFinite.
  4. The bad value surfaces while scoring. Documents [1, 2, 3], [-1, 2, 3], [0, 2, 3], query [+Inf, 1, 2]: with assertions on, EUCLIDEAN and DOT_PRODUCT end in an AssertionError inside VectorUtil that does not point at the constructor argument (no message for EUCLIDEAN; not finite: ... from <raw shorts> for DOT_PRODUCT; COSINE scores all three 0); with assertions off, EUCLIDEAN scores all three 0 and DOT_PRODUCT gives doc 0 a score of +Infinity (default boost 1).

Who is affected

Only applications on unreleased main (and branch_11x) that build ConstKnnFloat16ValueSource themselves; no Lucene code or query parser does, and its author called it a benchmark helper. A NaN or infinite component, such as a float32 value of 65520 or more cast to float16, then gives a late AssertionError or silent 0 or +Infinity scores. No index damage.

Mechanism, terms and numbers636 words

ConstKnnFloat16ValueSource (lucene/queries, added with float16 vectors in #16383) returns the same float16 query vector for every document; with Float16KnnVectorFieldSource and Float16VectorSimilarityFunction inside a FunctionQuery it scores documents by exact similarity to that vector. Its constructor only null-checks the short[] (ConstKnnFloat16ValueSource.java:31-33), while its float32 twin ConstKnnFloatValueSource wraps the argument in VectorUtil.checkFinite (ConstKnnFloatValueSource.java:33) and the float16 kNN query, similarity query and field call VectorUtil.checkFiniteFloat16. So a NaN or infinite half is accepted, with assertions on and off, and reaches VectorUtil at scoring time: with -ea, EUCLIDEAN, DOT_PRODUCT and MAXIMUM_INNER_PRODUCT fail an internal assert Float.isFinite(...) and COSINE silently scores 0; with -da, documents score 0 through FunctionQuery's intended NaN/negative clamp, or +Infinity under DOT_PRODUCT and MAXIMUM_INNER_PRODUCT with an infinite component (at the default boost of 1). Float.floatToFloat16 maps any float32 of magnitude 65520 or more to infinity, so one large component is enough. It is a missing fail-fast check and a parity gap with the twin, fixed by one line, on unreleased main only (also on branch_11x); no index damage, the match set is unchanged, and no Lucene code builds the class.

Terms

  • ConstKnnFloat16ValueSource (org.apache.lucene.queries.function.valuesource): "Function that returns a constant float16 vector value for every document." (ConstKnnFloat16ValueSource.java:27). Its float16VectorVal(doc) returns the stored array for every document (:40-42).
  • ConstKnnFloatValueSource: the float32 twin, in the same package since #12253 (9.7.0).
  • VectorUtil.checkFiniteFloat16 (VectorUtil.java:494-513): throws IllegalArgumentException when a half has all five exponent bits set, (v[i] & 0x7C00) == 0x7C00 (:507); "@throws IllegalArgumentException if any component of the vector is not finite" (:503).

The bug

  1. The constructor is this.vector = Objects.requireNonNull(constVector, "constVector"); (ConstKnnFloat16ValueSource.java:31-33). Nothing checks the components.
  2. The float32 twin is this.vector = VectorUtil.checkFinite(Objects.requireNonNull(constVector, "constVector")); (ConstKnnFloatValueSource.java:32-34). The two files differ only in that line, the VectorUtil import, the element type and the names.
  3. checkFiniteFloat16 is called by KnnFloat16VectorQuery (:97), Float16VectorSimilarityQuery (:132) and KnnFloat16VectorField (:72, :97), not here.
  4. At search time FunctionQuery.AllScorer visits every document (DocIdSetIterator.all, FunctionQuery.java:115); Float16VectorSimilarityFunction.func returns similarityFunction.compare(v1, v2) with the document's vector and the constant (Float16VectorSimilarityFunction.java:46).
  5. With -ea, VectorUtil.squareDistance(short[], short[]) (EUCLIDEAN) and dotProduct(short[], short[]) (DOT_PRODUCT, MAXIMUM_INNER_PRODUCT) assert a finite result (VectorUtil.java:149, :84-91) and throw an AssertionError: with no message from squareDistance, with not finite: <result> from <a>,<b> (the raw shorts) from dotProduct; neither names the constructor argument. cosine(short[], short[]) has no such assert (:117-123).
  6. With -da, FunctionQuery's score maps NaN and negative values to 0 (if (val >= 0 == false) ... return 0;, FunctionQuery.java:132-133), which is intended; a positive infinity passes the clamp and the score is boost * val (:135), so at the default boost of 1 it stays +Infinity.

The numbers

Executed on main 645d4fe14a20: three documents with float16 vectors [1, 2, 3], [-1, 2, 3], [0, 2, 3]; query [h, 1, 2] with h each of the 5 non-finite halves 0x7C00 (+Infinity), 0xFC00 (-Infinity), 0x7E00, 0x7C01, 0xFFFF (NaN); default boost 1.

similarityassertions on (-ea)assertions off (-da)
EUCLIDEANAssertionError (no message) at VectorUtil.squareDistanceall 3 documents score 0
DOT_PRODUCT, MAXIMUM_INNER_PRODUCTAssertionError: not finite: ... from <...> at VectorUtil.dotProductNaN: all 0; +Inf: doc 0 scores +Infinity, the others 0; -Inf: doc 1 scores +Infinity, the others 0
COSINEno assertion, all 3 score 0all 3 score 0

Float.floatToFloat16: 65504 and 65519 give 0x7BFF (65504), 65520 gives 0x7C00 (+Infinity), -100000 gives 0xFC00 (-Infinity), NaN gives 0x7E00 (OpenJDK 25.0.3). On OpenJDK 25 Lucene's vectorized code hands the float16 (short[]) kernels to the scalar implementation "until JDK 27" (PanamaVectorUtilSupport.java:310-325); the asserts sit in VectorUtil itself, above that choice.

Exposure and likelihood345 words
  • Who is hit. Code on main that constructs ConstKnnFloat16ValueSource from a query vector with a NaN or infinite half. No Lucene code builds the class (it appears only in its own file) and no query parser does. In the #16383 review its author said it serves the benchmark's exact-scoring path and is "not needed in production path".
  • How a bad vector arises. Float.floatToFloat16 turns any float32 of magnitude 65520 or more into an infinity and keeps NaN, so a float32 query with one large component, or a NaN from an upstream normalization, is enough.
  • What happens. The constructor accepts it and the failure moves to scoring: with -ea (Lucene's gradle test runs enable it for about 75% of seeds by default) an AssertionError from inside VectorUtil that does not point at the constructor argument (no message for EUCLIDEAN; not finite: ... from <raw shorts> for DOT_PRODUCT and MAXIMUM_INNER_PRODUCT); with -da (production) silent 0 scores, or +Infinity for one document under DOT_PRODUCT or MAXIMUM_INNER_PRODUCT. The same vector gets a clear IllegalArgumentException from KnnFloat16VectorQuery.
  • What does not happen. No hang and no extra work (the per-document work is the same as for a finite vector), no change to which documents match (FunctionQuery matches every document), no index effect (the class is query-side only and KnnFloat16VectorField rejects non-finite halves at index time).
  • Default vs opt-in, scale. Default codec and searcher suffice, but the API is opt-in and rare: float16 vectors, the @lucene.experimental FunctionQuery and a non-finite query vector. Any index size; the repro needs no index at all. Likelihood: rare.
  • The same through FunctionScoreQuery. The value source can also be scored with FunctionScoreQuery through ValueSource.asDoubleValuesSource(), an adapter from 2017 (LUCENE-7610); #15329 (for #15328) made the vector-similarity value source implement doubleVal, "allowing its use in FunctionScoreQuery". FunctionScoreQuery has its own equivalent clamp: a missing, negative or NaN value scores 0 (FunctionScoreQuery.java:246-251). Executed with MatchAllDocsQuery as the inner query, the same three documents and the halves 0x7C00, 0xFC00, 0x7E00: the outcomes match the table above, with -ea and -da.

Minimal repro

One JUnit test, one statement: new ConstKnnFloat16ValueSource(new short[] {Float.floatToFloat16(Float.NaN)}) inside expectThrows(IllegalArgumentException.class, ...). No index, no randomness, single thread, about 1.5 s; the same result with assertions on and off.

TestConstKnnFloat16ValueSourceNonFinite.java (JUnit, one test)30 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.queries.function;

import org.apache.lucene.queries.function.valuesource.ConstKnnFloat16ValueSource;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestConstKnnFloat16ValueSourceNonFinite extends LuceneTestCase {

  public void testNaNQueryVectorIsRejected() {
    // expected IllegalArgumentException, as ConstKnnFloatValueSource throws for a NaN float; actual: accepted
    expectThrows(
        IllegalArgumentException.class,
        () -> new ConstKnnFloat16ValueSource(new short[] {Float.floatToFloat16(Float.NaN)}));
  }
}
Command
cp TestConstKnnFloat16ValueSourceNonFinite.java $LUCENE/lucene/queries/src/test/org/apache/lucene/queries/function/
cd $LUCENE && ./gradlew :lucene:queries:test --tests org.apache.lucene.queries.function.TestConstKnnFloat16ValueSourceNonFinite -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false instead (same result)
Expected
IllegalArgumentException from the constructor, as ConstKnnFloatValueSource throws for a NaN float.
Observed
Expected exception IllegalArgumentException but no exception was thrown: the NaN vector is accepted, with assertions on and off.

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

More files, environment, full output194 words

TestConstKnnFloat16ValueSourceNonFinite builds the value source from a 1-component vector holding the NaN half 0x7E00, the smallest input, and expects IllegalArgumentException, as ConstKnnFloatValueSource throws for a NaN float. Public API only; no index or directory; the seed does not matter. It compiles only where the float16 API exists (main from bfe5c847d3c). The scoring-time symptoms (the table above) follow from the accepted vector and are deliberately not asserted.

As the regression test: this same test, for example as a new method in TestKnnVectorSimilarityFunctions. With the fix it passes with assertions on and off. Its comment line is longer than 100 columns, which google-java-format flags; with the line wrapped the module's static checks pass.

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT); no release has the class
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux, aarch64
Command (environment)
cp TestConstKnnFloat16ValueSourceNonFinite.java $LUCENE/lucene/queries/src/test/org/apache/lucene/queries/function/
cd $LUCENE && ./gradlew :lucene:queries:test --tests org.apache.lucene.queries.function.TestConstKnnFloat16ValueSourceNonFinite -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false instead (same result)

Observed output

TestConstKnnFloat16ValueSourceNonFinite > testNaNQueryVectorIsRejected FAILED
    junit.framework.AssertionFailedError: Expected exception IllegalArgumentException but no exception was thrown
        at __randomizedtesting.SeedInfo.seed([DEADBEEF:8D0C32D4C80B627D]:0)
        at org.apache.lucene.tests.util.LuceneTestCaseParent.expectThrows(LuceneTestCaseParent.java:1432)
        at org.apache.lucene.tests.util.LuceneTestCaseParent.expectThrows(LuceneTestCaseParent.java:1418)
        at org.apache.lucene.queries.function.TestConstKnnFloat16ValueSourceNonFinite.testNaNQueryVectorIsRejected(TestConstKnnFloat16ValueSourceNonFinite.java:26)

# the same with -Ptests.asserts=false; with the fix: 1 test, 0 failures, assertions on and off

Why it looks unintended

  • The float32 twin has rejected non-finite vectors since 9.7.0; a reviewer asked for that check "so no bad thing happen".
  • CHANGES 9.7.0: "Require indexed KNN float vectors and query vectors to be finite." The float16 query and field enforce it.
  • #16383 added the class "to be consistent with" the float32 one; nothing records a deliberate exception.
The full argument238 words
  • The twin's check was requested in review. In #12253 uschindler wrote that ConstKnnFloatVectorValueSource "should call VectorUtil.checkFinite(vector) in its constructor to make sure the float array is finite (like the Lucene KNNQuery and Field types), so no bad thing happen", and suggested exactly today's line.
  • The rule for the kNN APIs. CHANGES.txt (9.7.0): "GITHUB#12281: Require indexed KNN float vectors and query vectors to be finite." It set this for Lucene's kNN field and query APIs; it is not stated as a contract for every value source.
  • The PR that added the class applied the rule to its siblings. In the #16383 review danmuzi wrote on KnnFloat16VectorQuery: "The float32 implementation validates the target vector", and that a NaN from compare() "is only caught when assertions are enabled", proposing checkFiniteFloat16; they asked for the same on KnnFloat16VectorField; both landed. On this class the author said it is "just there to be consistent with other classes ConstKnnFloatValueSource and ConstKnnByteValueSource", and msokolov replied "we may as well follow the existing pattern". Nobody discussed validation for this class.
  • No sign of intent. No test covers the class or any float16 function-query class; no comment, CHANGES entry or javadoc allows non-finite input here.
  • A similar parity fix. #16708 (backport #16732) closed a similar validation-parity gap: columnar addBatch skipped the Knn*VectorField checks. CHANGES.txt lists it under Improvements (10.6.0), so it is precedent for this kind of hardening, not for calling it a bug.
The devil's-advocate check172 words

The strongest case for "not a bug" (the intended-behaviour devil's advocate and the cross-model "intended" review): no javadoc promises the exception (neither constructor has javadoc, and VectorSimilarityFunction.compare(short[], short[]) states no finiteness precondition); FunctionQuery is @lucene.experimental and clamps NaN to 0 on purpose; the class is a benchmark helper; and Lucene does not validate query vectors everywhere: DoubleValuesSource.similarityToQueryVector(..., short[], ...), FullPrecisionFloat16VectorSimilarityValuesSource and RescoreTopNQuery.createFullPrecisionRescorerQuery(short[]) accept a NaN query vector too (executed).

Why it fails: those three paths' float32 twins do not validate either, so they match their twins; of the four float32 classes that call VectorUtil.checkFinite (KnnFloatVectorField, KnnFloatVectorQuery, FloatVectorSimilarityQuery, ConstKnnFloatValueSource), only ConstKnnFloatValueSource has a float16 counterpart that does not validate. The twin's check was asked for in review, the class was added "to be consistent" with the twin, the same PR added checkFiniteFloat16 for exactly this input, and #12281 set the finite-vector rule for the kNN field and query APIs. Residual: a maintainer may call it hardening rather than a bug; not every other float16 entry point validates.

Suggested fix

Recommended A. Validate in the constructor with checkFiniteFloat16, as the twin does

Wrap the argument in VectorUtil.checkFiniteFloat16, exactly as the float32 twin uses VectorUtil.checkFinite: one line plus the import. With it the repro passes with assertions on and off, all 112 org.apache.lucene.queries.function tests pass, and the module's static checks pass.

All options and trade-offs4 options ยท 437 words

Option A was validated through gradle in scratch worktrees of 645d4fe14a20; B, C and D were not run.

A. Validate in the constructor with checkFiniteFloat16, as the twin does (validated by the checks) (recommended)

this.vector = VectorUtil.checkFiniteFloat16(Objects.requireNonNull(constVector, "constVector")); A non-finite vector then fails at construction with IllegalArgumentException: non-finite float16 value at vector[0]=NaN (the helper's message format).

--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloat16ValueSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloat16ValueSource.java
@@ -23,13 +23,14 @@
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.queries.function.FunctionValues;
 import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.util.VectorUtil;
 
 /** Function that returns a constant float16 vector value for every document. */
 public class ConstKnnFloat16ValueSource extends ValueSource {
   private final short[] vector;
 
   public ConstKnnFloat16ValueSource(short[] constVector) {
-    this.vector = Objects.requireNonNull(constVector, "constVector");
+    this.vector = VectorUtil.checkFiniteFloat16(Objects.requireNonNull(constVector, "constVector"));
   }
 
   @Override

Trade-offs

  • Parity with ConstKnnFloatValueSource, KnnFloat16VectorQuery, Float16VectorSimilarityQuery and KnnFloat16VectorField; the error names the bad component.
  • Cost: one bitmask test per component, once per construction.
  • Behaviour changes only for non-finite input, which no in-tree code passes.
  • Like the twin, it checks once and keeps the caller's array, so later changes to that array are not caught.
  • The changed line is exactly 100 columns; google-java-format accepts it.

What the checks validated

  • Gradle, scratch worktree of 645d4fe14a20, seed DEADBEEF, 2026-11-01: without the fix the repro fails with assertions on and off; with it, it passes with assertions on and off.
  • With the fix, ./gradlew :lucene:queries:test --tests 'org.apache.lucene.queries.function.*': 15 classes, 112 tests, 0 failures, 0 skipped, including TestKnnVectorSimilarityFunctions (11) and the repro.
  • ./gradlew :lucene:queries:check -x test with the fix and the repro as a new test file: google-java-format failed only on the repro's comment line, not on the fix, and the build stopped there, so ecj lint and forbidden APIs did not run in that run; with the line wrapped, the module's static checks pass (google-java-format, ecj lint, forbidden APIs; error-prone skipped). The full Lucene test suite was not run.
  • Earlier, with the fix shadowed into the stock jars: the finder's, the independent reproducer's and the cross-model reviewers' tests pass, on ea57360912ed and 645d4fe14a20.

B. Copy the array and validate the copy

VectorUtil.checkFiniteFloat16(constVector.clone()) after the null check.

Trade-offs

Also guards against later changes to the caller's array (the copy question was raised in the #12253 review). Costs one allocation per construction and departs from the twin, which does not copy.

C. Validate at scoring time

Check the vectors in Float16VectorSimilarityFunction.func.

Trade-offs

Catches any non-finite input, but costs a check per document, and document vectors are already validated at index time. Not recommended.

D. Document the behaviour

Say in the javadoc that non-finite components give unspecified scores.

Trade-offs

Keeps the asymmetry with the twin and with the float16 query and field. Not recommended.

Downstream note

No released version has the class; an application on main that passes user-supplied query vectors to it can reject non-finite components itself until a fix lands, as Lucene's own float16 kNN query does.

How it was found

Non-formal: an automated comparison rewrote each float16 class into its float32 twin's spelling and diffed the pair; only this class drops the twin's finiteness check. A JUnit test on the stock jars then confirmed it with assertions on and off.

The method in full136 words
  • Method: twin diff, the hunt's non-formal tier, in the hunt unit on float16 vectors. Every float16 class was rewritten into its float32 twin's spelling and diffed; the ConstKnnFloat16ValueSource pair differs only in the missing VectorUtil import and checkFinite call.
  • Alongside: a bounded-exhaustive check over all 65,536 half bit patterns showed that KnnFloat16VectorField, KnnFloat16VectorQuery and Float16VectorSimilarityQuery reject exactly the non-finite halves; it did not cover this class, which is why the diff found it.
  • Witness: a 3-test JUnit on the stock main jar (float32-twin control, +Infinity, end-to-end EUCLIDEAN scoring) failed 2 of 3 with -ea and -da; a shadowed one-line fix made it pass.
  • Later: an independent reproducer, an intended-behaviour devil's advocate, cross-model reviews and an adjudication checked it (2026-10-25); the test was reduced to one statement (2026-10-29) and re-verified on main 645d4fe14a20.

Prior reports, history and checks

Prior reports

No prior report found in GitHub issues, PRs and review threads, commits, the JIRA archive, the mailing lists, downstream projects or the web (searched 2026-10-25 and 2026-10-26, re-checked 2026-11-01). The closest material is the #16383 review, which added the check to the float16 query and field but not here.

Searches4 searches

Closest items, none a report of this bug: #16383 (introduced the class; its review added the check to the float16 query and field, not here), #12253 (the twin's check, requested in review), #12281 (the finite-vector rule, 9.7.0), #16735 (Float16VectorSimilarityQuery, validates), #16725 (the open float16 tracker; it does not mention function-query value sources), #16708 / #16732 (a similar validation-parity gap, closed; CHANGES lists it as an Improvement). Gaps: a search shows only that no report was found; private channels were not searched.

What was searched, when, and what turned up
WhereWhenHowFound
apache/lucene issues, PRs, commits and review threads2026-10-25, 2026-10-26five scripted prior-report searches (46 issue/PR and 8 commit queries); 101 manual read-only searches; the changed files of all 412 open PRs; the review threads of #16383, #12253, #12281, #16708, #14078, #15979 and #16795 read directlyno report; #16383's review is the closest material
Mailing lists, JIRA archive, downstream, web2026-10-25, 2026-10-2668 list queries over dev@, java-user@, issues@ and commits@; 7 JIRA-archive queries; Elasticsearch, OpenSearch, Solr and luceneutil; 23 web queriesno report (list hits are notification mirrors of the related PRs)
Re-check: apache/lucene issues and PRs2026-11-01read-only searches sorted by creation date for ConstKnnFloat16ValueSource, checkFiniteFloat16, Float16VectorSimilarityFunction, "float16 NaN" and "fp16 non-finite" (issues; the first three also PRs)nothing new: 0 issue hits; PR hits only #16383 and #16735, both already known
Upstream main and branches2026-11-01file contents at upstream main 2f424fd428da (2026-10-30) and branch_11x 603af870375not fixed; the file is unchanged since #16383

History

Introduced with float16 vectors by #16383 (bfe5c847d3c, merged 2026-07-27); the class never validated. No release has it: 10.6.0, 10.5.2 and 9.12.3 lack the float16 API. branch_11x carries the identical file, so 11.0.0 would ship it if nothing changes.

Versions tested5 rows

Introduced by #16383 "Add fp16 (float16) vector encoding support - no quantization support" (GitHub Pulkitg64; approved by danmuzi, jeho-rpls and msokolov), merge commit bfe5c847d3c on main, 2026-07-27. git log on the file returns only this commit. Not a regression: the class never validated, while its float32 twin has validated since 14c18d86245 (#12253, 2023-06-14, first in 9.7.0). The same commit added VectorUtil.checkFiniteFloat16 and used it in the float16 query and field; #16735 (2026-09-29) used it in Float16VectorSimilarityQuery.

Versions and branches executed, with results
Version or commitVerdictResult
main 645d4fe14a20 affectedminimal repro fails with assertions on and off (standalone and gradle); passes with fix A
main ea57360912ed, bfe5c847d3c (#16383) introducing committhe finder's repro fails 2 of 3 with -ea and -da; passes with the fix at bfe5c847d3c
upstream main 2f424fd428da, branch_11x 603af870375 readidentical file (blob 4ca6dd9d5799), not executed
10.5.2, 10.5.0, 9.12.3, parent 606a26dafd55 not affectedno float16 API: the repro does not compile; the float32 twin rejects non-finite vectors
10.6.0, branch_10_6, branch_10x readno class; 10.6.0 and branch_10_6 have no float16 API (no KnnFloat16VectorField, no FLOAT16 encoding; 10.6.0 does not contain bfe5c847d3c)

Independent checks

Confirmed with high confidence by an independent reproducer, an intended-behaviour devil's advocate, reviews by a reviewer from another model family (OpenAI GPT-5.6) and an adjudication; none dissented. Re-verified on main 645d4fe14a20; this doc's gradle run validated the fix.

Checks and verdicts301 words

Seven checks; all agree on the root cause. The reviews narrowed the claim: the 0 score is FunctionQuery's intended clamp, COSINE gives no AssertionError, and other float16 query-vector paths that skip validation match their float32 twins.

Each independent check and its verdict
CheckVerdictWhat it found
cross-model review, refute lens (OpenAI GPT-5.6) real, high confidenceIts own test fails under -ea and -da with -Infinity and NaN, a finite control passes, and the fix passes it and TestKnnVectorSimilarityFunctions (11/0). It corrected the claim: the 0 score is the intended clamp.
independent reproducer realIts own 5-test repro fails 2 of 5 under -ea and -da: all 5 non-finite halves are accepted while the twin and the float16 siblings reject them; it mapped the outcome per similarity; its own one-line fix passes 5/5.
cross-model review, intended lens (OpenAI GPT-5.6) real, high confidenceRated the intended-behaviour case moderate (experimental FunctionQuery, no @throws) and rejected it because of the validating twin, the float16 siblings and the #16383 history; its tests were re-run locally with new seeds.
intended-behaviour devil's advocate real, low severityNo intended-behaviour argument survives; found the other unvalidated float16 query-vector paths and showed that their float32 twins do not validate either.
adjudication confirmed, high confidenceRe-ran the reproducer's test with a new seed (2 of 5 fail under -ea and -da, 5/5 with the fix) and framed it as a missing fail-fast check, not a scoring bug.
main refreshes still reproducesRe-run on main a9779ef3c193 and 645d4fe14a20 with the same failures; reduced to a one-statement test on 2026-10-29.
brief validation (gradle) fix A validatedRepro fails on stock main with assertions on and off and passes with fix A; the org.apache.lucene.queries.function tests and the module's static checks pass.

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

Finding record

LFV-092, snapshot of 2026-11-06

Registry title
ConstKnnFloat16ValueSource accepts Inf/NaN float16 query vectors that its float32 twin ConstKnnFloatValueSource rejects (missing VectorUtil.checkFiniteFloat16)
Area
queries / function vector sources
Priority
Low, rank 1167: ConstKnnFloat16ValueSource accepts NaN/Inf query halves that its float32 twin rejects, a benchmark-helper class in unreleased code with no index damage.
Impact class
wrong error
Impact note
Only applications on unreleased main and branch_11x (11.0.0) that construct a ConstKnnFloat16ValueSource themselves from a float16 query vector with an infinite or NaN half (for example a float32 query component of magnitude >= 65520 converted with Float.floatToFloat16) and score it with a FunctionQuery over Float16VectorSimilarityFunction; default codec and searcher, any scale; no index damage, no Lucene code or query parser builds the class, and its author called it a benchmark exact-scoring helper.
Summary
You build a ConstKnnFloat16ValueSource from a float16 query vector that contains an infinity or NaN half and score it against a KnnFloat16VectorField with a FunctionQuery over Float16VectorSimilarityFunction. You expect the constructor to throw IllegalArgumentException, as its float32 twin ConstKnnFloatValueSource does (VectorUtil.checkFinite) and as KnnFloat16VectorQuery and KnnFloat16VectorField do for the same halves (VectorUtil.checkFiniteFloat16). Instead the constructor only null-checks the array, so with -ea scoring later fails with an AssertionError inside VectorUtil (no message for EUCLIDEAN; 'not finite: ... from <raw shorts>' for DOT_PRODUCT and MAXIMUM_INNER_PRODUCT; COSINE silently scores 0), and with -da documents score 0 through FunctionQuery's intended NaN/negative clamp or, for DOT_PRODUCT and MAXIMUM_INNER_PRODUCT with an infinite component, +Infinity.
Introduced
bfe5c847d3c (#16383 'Add fp16 (float16) vector encoding') [read: git log, --diff-filter=A and git blame on ConstKnnFloat16ValueSource.java return only this commit, and the file is byte-identical at ea57360912ed; exec: the finder repro fails at bfe5c847d3c under -ea and -da and passes there with the one-line fix, and its parent 606a26dafd55 has no FLOAT16 API]; not a regression: the class has never validated, while its float32 twin has validated since 14c18d86245 (#12253, 2023-06-14) (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 645d4fe14a2 [exec: both fail under -ea and -da at ea57360912ed and 645d4fe14a2 (run 2026-10-29)]; branch_11x 603af870375 has the identical file [read 2026-11-01]; 10.5.2, 10.5.0, 9.12.3 and the parent 606a26dafd55 not affected (no FLOAT16 API, the repro does not compile) [exec]; the float32 twin rejects non-finite vectors on 9.12.3, 10.5.0, 10.5.2, 606a26dafd55, bfe5c847d3c and main [exec control]; branch_10x 657014b3c57 and branch_10_6 4ae946e5a9b do not have the class [read 2026-10-29]; 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
Upstream and prior reports
none found (2026-10-25 and 2026-10-26: 5 scripted prior-report searches with 46 GitHub issue/PR, 8 commit, 6 downstream, 68 mailing-list (dev@, java-user@, issues@, commits@), 7 JIRA-archive and 23 web queries; 101 manual read-only GitHub searches; changed-file lists of all 412 open PRs; review threads of #16383, #12253, #12281, #16708, #14078, #15979 and #16795 read directly); related prior items: #16383 (introduced the class; its review added checkFiniteFloat16 to the fp16 query and field because 'The float32 implementation validates the target vector', and separately kept this class to 'follow the existing pattern', but never discussed validation here), #12253 (the float32 twin's checkFinite, requested in review), #12281 (finite query and indexed vectors, CHANGES 9.7.0), #16735 (Float16VectorSimilarityQuery, validates), #16725 (open fp16 completion tracker, does not mention it), #16708 and its backport #16732 (a similar validation-parity gap, closed; CHANGES lists it under Improvements); no open PR touches the file
Lucene commit tested
main 645d4fe14a20: the minimal repro with assertions on and off, standalone and through gradle; the recommended fix in scratch worktrees with the org.apache.lucene.queries.function tests (15 classes, 112 tests) and :lucene:queries:check -x test; the independent reproducer's end-to-end test (every similarity, 5 non-finite halves). Main ea57360912ed and the introducing commit bfe5c847d3c: the finder's repro. 10.5.2, 10.5.0, 9.12.3 and the parent 606a26dafd55: no float16 API (the repro does not compile; the float32 twin rejects non-finite vectors). 10.6.0, branch_10_6 and branch_10x do not have the class; upstream main 2f424fd428da and branch_11x 603af870375 have the identical file (read).

Lucene and upstream sources

Finding record
  • lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloat16ValueSource.java:27-33 (class javadoc 'returns a constant float16 vector value for every document'; constructor stores Objects.requireNonNull(constVector, "constVector") with no finiteness check)
  • lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloatValueSource.java:32-34 (float32 twin: this.vector = VectorUtil.checkFinite(Objects.requireNonNull(constVector, "constVector")))
  • lucene/core/src/java/org/apache/lucene/util/VectorUtil.java:495-513 (checkFiniteFloat16, '@throws IllegalArgumentException if any component of the vector is not finite'), :79-91 and :144-151 (dotProduct and squareDistance on short[] assert a finite result), :118-123 (cosine on short[] has no such assert)
  • lucene/core/src/java/org/apache/lucene/search/KnnFloat16VectorQuery.java:97, lucene/core/src/java/org/apache/lucene/search/Float16VectorSimilarityQuery.java:132, lucene/core/src/java/org/apache/lucene/document/KnnFloat16VectorField.java:72, 97 (checkFiniteFloat16 on query targets and field values)
  • lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java:38 (@lucene.experimental), :115 (AllScorer iterates DocIdSetIterator.all), :129-136 (score maps NaN and negative values to 0)
  • lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/Float16VectorSimilarityFunction.java:40-47 (func calls similarityFunction.compare on the constant and the document vector)
  • lucene/CHANGES.txt:3510 ('GITHUB#12281: Require indexed KNN float vectors and query vectors to be finite.')
The finding in 30 seconds
  • lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloat16ValueSource.java:27, 31-33, 40-42
  • lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloatValueSource.java:32-34
  • lucene/core/src/java/org/apache/lucene/util/VectorUtil.java:79-93, 117-123, 144-151, 494-513
  • lucene/core/src/java/org/apache/lucene/search/KnnFloat16VectorQuery.java:97; Float16VectorSimilarityQuery.java:132; lucene/core/src/java/org/apache/lucene/document/KnnFloat16VectorField.java:72, 97
  • lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java:115, 129-137 (boost * val at :135); lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/Float16VectorSimilarityFunction.java:36-47
  • lucene/core/src/java25/org/apache/lucene/internal/vectorization/PanamaVectorUtilSupport.java:59, 310-325 (float16 kernels delegated to the scalar code until JDK 27)
Who is affected
  • lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java:38 (@lucene.experimental), :115
  • grep -rn ConstKnnFloat16ValueSource lucene --include='*.java' (only its own file)
  • build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/TestsAndRandomizationPlugin.java:240-248 (tests.asserts default: ~75% of seeds)
Why it looks unintended
  • lucene/CHANGES.txt:3543; :529, 537-539 (#16708 under Improvements, 10.6.0 section)
  • git grep -n 'checkFinite(' at 645d4fe14a20 in lucene/*/src/java (four float32 callers); lucene/core/src/java/org/apache/lucene/document/column/ColumnValidation.java:476 (float16 column check)
Suggested fix
  • lucene/core/src/java/org/apache/lucene/util/VectorUtil.java:503-511 (message format)
Prior reports
  • a Lucene clone: git rev-parse origin/main:<path>, origin/branch_11x:<path> (blob 4ca6dd9d5799)
History
  • a Lucene clone (2026-11-01): git rev-parse <ref>:<path> of ConstKnnFloat16ValueSource.java and KnnFloat16VectorField.java, git grep FLOAT16 VectorEncoding.java, for origin/main, origin/branch_11x, origin/branch_10_6, origin/branch_10x, releases/lucene/10.6.0; git merge-base --is-ancestor bfe5c847d3c releases/lucene/10.6.0 (false), origin/branch_11x (true)

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

Repro files

About the diagrams

Made in parallel with this doc from the mechanism in the brief; the PNG is the summary frame.

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.