All findings · Lucene finding · LFV-028 · huge-boost-parses-to-infinity

A boost too large for a float parses to Infinity and both query parsers throw IllegalArgumentException

A query such as a^ followed by 39 nines, a boost too large for a float (the largest is about 3.4e38), makes QueryParser.parse() throw IllegalArgumentException instead of its declared ParseException; the flexible StandardQueryParser does the same instead of QueryNodeException. Only the exception type is wrong, in every release since 8.0.0.

Summary

Priority
Low, rank 1053A 39-digit boost literal overflows to Infinity and both query parsers let IllegalArgumentException escape instead of ParseException, an input only fuzzers or hostile queries produce.
Impact
wrong error
Since
8.0.0 (LUCENE-7996, commit a8a63464e7da, 2017-12-06)introduced 2017-12-06 · about 8 years
Affected
8.0.0-10.6.0 and main
Status
documented
Found by
non-formal baseline: fuzzing of the query parsers

AI-assisted page: this finding was first signalled by a testing baseline (randomized, fuzz, property-based or stress testing, or static analysis) 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

The grammar accepts a boost of any length, such as 39 nines, but that is too big for a float.

Background: boosts in the query syntax

  1. In the classic and flexible query syntax, term^N multiplies a clause's score by N: apache^4 makes matches on apache count four times as much. N is plain digits with an optional fraction: no sign, no exponent, no length limit.
  2. Each parser turns N into a Java float with Float.parseFloat and wraps the clause in a BoostQuery. Since 8.0.0, BoostQuery rejects a negative, NaN or infinite boost with IllegalArgumentException.
Diagram of the bug: the query a^ followed by 39 nines is accepted by the grammar's NUMBER token; Float.parseFloat turns it into Infinity without throwing, so handleBoost's 'no boost' fallback never runs; new BoostQuery(q, Infinity) throws IllegalArgumentException: boost must be a positive float, got Infinity; parse() converts only ParseException, TokenMgrError and TooManyClauses, so the IllegalArgumentException escapes instead of ParseException. Finite boosts up to Float.MAX_VALUE (3.4028235E38) work. The fix checks Float.isFinite and throws ParseException.
A boost of 39 nines passes the grammar, Float.parseFloat rounds it to Infinity, and BoostQuery's finite check throws past parse()'s ParseException handling. Select the still to open it at full size. Generated with AI, checked against the code.

The finding, step by step

  1. Parse a^3 with the classic QueryParser: you get (f:a)^3.0. Even a^340282346638528859811704183484516925440, the decimal value of Float.MAX_VALUE, works: (f:a)^3.4028235E38.
  2. Now parse a^999999999999999999999999999999999999999 (39 nines, about 1.0e39). You expect ParseException, the only exception parse() declares, as you get for the same number after ~.
  3. Instead Float.parseFloat rounds the boost to Infinity without throwing, new BoostQuery(q, Infinity) rejects it, and IllegalArgumentException: boost must be a positive float, got Infinity escapes parse().
  4. The flexible StandardQueryParser fails the same way instead of throwing QueryNodeException. Every boost literal from 2^128 - 2^103 (39 digits) up overflows, at every boost site, in every release since 8.0.0.

Who is affected

Applications that parse end-user query strings with the classic QueryParser (or its subclasses) or the flexible StandardQueryParser, with default settings, and catch only the declared exception. The trigger is contrived, a boost literal of at least 2^128 - 2^103, which takes 39 or more digits; one request then fails with the wrong exception, and no query is built.

Mechanism, terms and numbers577 words

The classic and flexible query parsers read the boost after ^ as the grammar's NUMBER token, digits with no length limit, and convert it with Float.parseFloat. For a literal of 2^128 - 2^103 = 340282356779733661637539395458142568448 or more (39 digits), Float.parseFloat returns Infinity without throwing, so QueryParserBase.handleBoost's "invalid boost means no boost" catch never runs. new BoostQuery(q, Infinity) then throws IllegalArgumentException("boost must be a positive float, got Infinity"), and parse(), which declares only ParseException and converts only ParseException, TokenMgrError and TooManyClauses, lets it through. The flexible StandardQueryParser builds a BoostQueryNode with value Infinity and hits the same exception in BoostQueryNodeBuilder.build instead of a QueryNodeException. The same literal after ~ already gets the declared exception in both parsers. No query is built, so nothing is matched or scored wrongly: only the exception type is wrong, and the trigger is contrived. BoostQuery's finite check came with LUCENE-7996 in 8.0.0, which did not touch the parsers; 7.x silently built (f:lucene)^Infinity. A Float.isFinite check in handleBoost and in BoostQueryNodeBuilder fixes both parsers.

Terms

  • Classic parser: org.apache.lucene.queryparser.classic.QueryParser (and its subclasses MultiFieldQueryParser, ComplexPhraseQueryParser); parse(String) documents @throws ParseException if the parsing fails.
  • Flexible parser: StandardQueryParser (and PrecedenceQueryParser), which parses in three phases (syntax, processing, build); parse(String, String) documents @throws QueryNodeException.
  • Float.MAX_VALUE: about 3.4028235e38. A decimal literal of 2^128 - 2^103 = 340282356779733661637539395458142568448 or more rounds to Infinity; one less rounds to Float.MAX_VALUE.

The bug

  1. The boost token is <NUMBER: (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? > in both grammars (QueryParser.jj:218, StandardSyntaxParser.jj:160).
  2. Classic handleBoost (QueryParserBase.java:813-831) does f = Float.parseFloat(boost.image) (:817) inside a catch whose comment says an invalid boost "defaults to no boost" (:821-823). On overflow Float.parseFloat returns Infinity and does not throw, so that fallback never runs, and q = new BoostQuery(q, f) (:828) throws.
  3. BoostQuery's constructor (BoostQuery.java:40-41): if (Float.isFinite(boost) == false || Float.compare(boost, 0f) < 0) throw new IllegalArgumentException("boost must be a positive float, got " + boost);.
  4. parse() (QueryParserBase.java:129-146) converts only ParseException | TokenMgrError and TooManyClauses into ParseException, so the IllegalArgumentException leaves parse() unwrapped.
  5. Flexible: the Boost production builds new BoostQueryNode(node, parseFloat(boost)) with no check (StandardSyntaxParser.jj:701), and BoostQueryNodeBuilder.build (:48) calls new BoostQuery(...) with Infinity. The neighbouring FuzzyOp production validates its number and throws ParseException (StandardSyntaxParser.jj:716-724), as classic handleBareFuzzy does (QueryParserBase.java:786-795).

The numbers

Stock main 645d4fe14a20, StandardAnalyzer, default field f (a small probe, not the repro):

classic  a^3                -> (f:a)^3.0
classic  a^<Float.MAX_VALUE> -> (f:a)^3.4028235E38
classic  a^<2^128-2^103>    -> java.lang.IllegalArgumentException: boost must be a positive float, got Infinity
classic  a^<39 nines>       -> java.lang.IllegalArgumentException: boost must be a positive float, got Infinity
flexible a^<39 nines>       -> java.lang.IllegalArgumentException: boost must be a positive float, got Infinity
classic  a~<39 nines>       -> ParseException: Cannot parse 'a~<39 nines>': Fractional edit distances are not allowed!
flexible a~<39 nines>       -> ParseException: Syntax Error, cannot parse a~<39 nines>:

The ~ rows show only the exception type: the classic message is wrong for this input, which reaches it because Infinity satisfies fms >= 1.0f && fms != (int) fms (QueryParserBase.java:791).

The same exception for phrase, group, range, fuzzy, wildcard, regexp and *:* boosts, and in MultiFieldQueryParser, ComplexPhraseQueryParser and PrecedenceQueryParser. A stop word as the boosted term hides it: the term is analysed away, handleBoost skips the BoostQuery, and the^<39 nines> gives an empty query.

Exposure and likelihood219 words
  • Who is hit. Callers of QueryParser, MultiFieldQueryParser, ComplexPhraseQueryParser, StandardQueryParser or PrecedenceQueryParser that handle bad input with catch (ParseException e) / catch (QueryNodeException e). Default configuration, any analyzer that keeps the boosted term, every boost site.
  • What it takes. A boost literal of at least 2^128 - 2^103, which is 39 digits, because the grammar has no exponent syntax. Nobody types that by accident; fuzzers and deliberately odd input produce it (rated "contrived").
  • What happens. The unchecked exception bypasses the application's "bad query" handler, so depending on the application a malformed query becomes an internal error instead of a validation error. Assertions on or off make no difference.
  • What does not happen. No query is returned, so nothing is matched or scored wrongly; no hang, no material resource use, no index effect, no state left behind.
  • Why it can matter more later. #9064 (open) proposes bounding boosts to [2^-10, 2^10]. If BoostQuery enforced that, ordinary literals such as ^2000 would take this same path, so the parsers need their own range check anyway.
  • Related, separate sites (not part of this bug). The surround parser accepts a^<39 nines> (weight Infinity) and the same exception appears later, in SrndQuery.makeLuceneQueryField. Other unchecked exceptions escape parse() for other inputs: an invalid regex (#7761) and a negative phrase slop (LFV-300).

Minimal repro

One JUnit test on the public API: the classic QueryParser with StandardAnalyzer parses a^ followed by 39 nines and must throw ParseException. Deterministic, single thread, no random-dependent logic; about a minute with gradle in a fresh checkout.

TestHugeBoostParseException.java (JUnit, one test)29 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.queryparser.classic;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestHugeBoostParseException extends LuceneTestCase {

  public void testBoostAboveFloatMaxValue() {
    QueryParser parser = new QueryParser("f", new StandardAnalyzer());
    // expected: ParseException (parse's only declared failure); actual: IllegalArgumentException
    expectThrows(ParseException.class, () -> parser.parse("a^" + "9".repeat(39)));
  }
}
Command
cp TestHugeBoostParseException.java $LUCENE/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/
cd $LUCENE && ./gradlew :lucene:queryparser:test --tests org.apache.lucene.queryparser.classic.TestHugeBoostParseException -Ptests.seed=DEADBEEF
# assertions off: add -Ptests.asserts=false (same result)
Expected
ParseException, the only exception parse() declares.
Observed
IllegalArgumentException: boost must be a positive float, got Infinity from BoostQuery.<init> via QueryParserBase.handleBoost:828; the same with assertions off.

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

More files, environment, full output250 words

StandardAnalyzer() no longer removes English stop words by default (LUCENE-7444), so a is kept; a stop word would hide the bug. The test sits in the classic package only to match Lucene's test layout and uses no package-private member.

What it encodes. It asserts the ParseException choice (fix A). Under the "no boost" alternative (fix B) the parser returns a query and this test fails with "Expected exception ParseException but no exception was thrown"; the assertion would then become "parse returns a query", with the same input.

As the regression test: the same test, for example as a method in TestQueryParser next to the boost tests.

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT); this test also fails on the 10.6.0 and 10.5.2 releases; the same boost literal (as `lucene^<39 nines>`) fails on every release from 8.0.0
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux, aarch64
Command (environment)
cp TestHugeBoostParseException.java $LUCENE/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/
cd $LUCENE && ./gradlew :lucene:queryparser:test --tests org.apache.lucene.queryparser.classic.TestHugeBoostParseException -Ptests.seed=DEADBEEF
# assertions off: add -Ptests.asserts=false (same result)

Observed output

# gradle, main 645d4fe14a20, seed DEADBEEF (assertions on; the same with -Ptests.asserts=false)
TestHugeBoostParseException > testBoostAboveFloatMaxValue FAILED
    junit.framework.AssertionFailedError: Unexpected exception type, expected ParseException but got java.lang.IllegalArgumentException: boost must be a positive float, got Infinity
        at __randomizedtesting.SeedInfo.seed([DEADBEEF:D0011E5C370269F9]:0)
        at app/org.apache.lucene.test_framework@11.0.0-SNAPSHOT/org.apache.lucene.tests.util.LuceneTestCaseParent.expectThrows(LuceneTestCaseParent.java:1437)
        at app/org.apache.lucene.test_framework@11.0.0-SNAPSHOT/org.apache.lucene.tests.util.LuceneTestCaseParent.expectThrows(LuceneTestCaseParent.java:1418)
        at app//org.apache.lucene.queryparser.classic.TestHugeBoostParseException.testBoostAboveFloatMaxValue(TestHugeBoostParseException.java:27)

        Caused by:
        java.lang.IllegalArgumentException: boost must be a positive float, got Infinity
            at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.search.BoostQuery.<init>(BoostQuery.java:41)
            at org.apache.lucene.queryparser.classic.QueryParserBase.handleBoost(QueryParserBase.java:828)
            at org.apache.lucene.queryparser.classic.QueryParser.Term(QueryParser.java:710)
            at org.apache.lucene.queryparser.classic.QueryParser.Clause(QueryParser.java:387)
            at org.apache.lucene.queryparser.classic.QueryParser.Query(QueryParser.java:262)
            at org.apache.lucene.queryparser.classic.QueryParser.TopLevelQuery(QueryParser.java:230)
            at org.apache.lucene.queryparser.classic.QueryParserBase.parse(QueryParserBase.java:133)

# release jars 10.6.0 and 10.5.2 (assertions on and off): the same failure (handleBoost:852 on 10.6.0, :893 on 10.5.2)

Why it looks unintended

  • parse() documents only @throws ParseException (flexible: QueryNodeException) and already converts the runtime TooManyClauses into ParseException.
  • The same 39-digit number after ~ gets the declared exception in both parsers, and handleBoost's own comment says an invalid boost means "no boost".
  • LUCENE-7996 added the finite check to BoostQuery for 8.0.0 without touching the parsers; 7.x returned (f:lucene)^Infinity.
The full argument303 words
  • Contract. QueryParserBase.java:127: @throws ParseException if the parsing fails. StandardQueryParser.java:274 and QueryParserHelper.java:211: @throws QueryNodeException if something wrong happens along the three phases; this exception comes from the build phase. The syntax docs say only "By default, the boost factor is 1. Although the boost factor must be positive, it can be less than 1" (classic/package-info.java:268-269): no upper bound, no failure mode. The flexible BoostQueryNode javadoc says "The boost value may vary from 0.0 to 1.0" (BoostQueryNode.java:29), a stale range that nothing enforces: flexible a^3 builds (f:a)^3.0. The BoostQuery constructor javadoc has no @throws.
  • Tests. Upstream QueryParserTestBase.testBooleanQuery (:985-989) asserts that TooManyClauses comes out as a parse exception, and testException (:976-983) asserts parse exceptions for malformed text. No test expects an IllegalArgumentException from parsing query text; the module's IAE tests cover parser setters and the static MultiFieldQueryParser.parse(String[], String[], ...) overloads, which document it for arrays of different lengths.
  • History. LUCENE-7996 (#9044, a8a63464e7da, 2017-12-06) touched 22 files, none under lucene/queryparser; its MIGRATE note says only "negative boosts are now rejected", which the grammar cannot produce. The issue thread and the dev@ heads-up mention neither the parsers nor infinite boosts. Before it, every 7.x release returned (f:lucene)^Infinity.
  • Precedent (mixed). In LUCENE-950 (#2025, 2007) FuzzyQuery's IllegalArgumentException escaped classic parse() for foo~1. The reporter, Eleanor Joslin, wrote "An application shouldn't blow up just because a user types something silly in a search field". Grant Ingersoll (@gsingers) first wrote "this isn't really an error"; Mark Miller (@markrmiller) later wrote "it seems silly to throw the illegal arg exception in either case". It was closed "Not A Problem" in 2010, the day Robert Muir (@rmuir) noted that the 4.0 fuzzy syntax had made foo~1 legal. Not a ruling that parse() may throw IAE.
The devil's-advocate check189 words

The strongest case for "not a bug" (the noise audit's first triage, and the refutation angles a tie-breaker checked): a throws ParseException clause never excludes unchecked exceptions; the input is absurd (39 digits) and the failure is loud and clear; no query is built, so no result is wrong; and other unchecked exceptions already escape parse() (#7761, LFV-300), so "always ParseException" was never achieved.

Why it fails: the input is end-user query text, for which parse() documents a parse exception as its failure, not a developer-chosen argument, and the grammar's own token accepts it. The parsers already convert a runtime failure and already reject the same literal after ~, and handleBoost intends a bad boost to mean "no boost". The loud failure is exactly what breaks a catch (ParseException e) handler. The other escapes are separate open bugs, not evidence of intent. The argument limits severity (low), not validity; the noise audit's challenger and the tie-breaker both kept the claim as Low. This is an inference from the documented contract and the code, not a maintainer ruling: a maintainer may treat it as a low-priority hardening request.

Suggested fix

Recommended A. Reject a non-finite boost in both parsers

Reject a non-finite boost with each parser's own exception: a Float.isFinite check in QueryParserBase.handleBoost (throws ParseException) and in BoostQueryNodeBuilder.build (throws QueryNodeException), +14/-1, no grammar regeneration. The repro passes with assertions on and off, and all 534 queryparser tests and check pass.

// QueryParserBase.handleBoost, now declared "throws ParseException", after Float.parseFloat:
if (Float.isFinite(f) == false) {
  throw new ParseException("Boost " + boost.image + " is out of range");
}
// BoostQueryNodeBuilder.build, before the BoostQuery is built:
if (Float.isFinite(boostNode.getValue()) == false) {
  throw new QueryNodeException(
      new MessageImpl(
          QueryParserMessages.INVALID_SYNTAX, "boost out of range: " + boostNode.getValue()));
}
All options and trade-offs3 options ยท 635 words

Option A was run through gradle in a scratch worktree of 645d4fe14a20 and is recommended. B and C are the alternatives a maintainer may prefer; B was checked only as a shadowed sketch, C was not tried. No open PR touches these lines.

A. Reject a non-finite boost in both parsers (validated by the checks) (recommended)

QueryParserBase.handleBoost gets throws ParseException and a finite check after Float.parseFloat; BoostQueryNodeBuilder.build gets the same check with a QueryNodeException (two imports). Both generated callers of handleBoost already declare throws ParseException, so no generated parser changes. PrecedenceQueryParser uses the same builder; MultiFieldQueryParser and ComplexPhraseQueryParser inherit handleBoost.

// QueryParserBase.handleBoost, now declared "throws ParseException", after Float.parseFloat:
if (Float.isFinite(f) == false) {
  throw new ParseException("Boost " + boost.image + " is out of range");
}
// BoostQueryNodeBuilder.build, before the BoostQuery is built:
if (Float.isFinite(boostNode.getValue()) == false) {
  throw new QueryNodeException(
      new MessageImpl(
          QueryParserMessages.INVALID_SYNTAX, "boost out of range: " + boostNode.getValue()));
}

Trade-offs

  • Matches how both parsers already treat a bad fuzzy number. Finite boosts up to Float.MAX_VALUE are unchanged. Error path only: no API, format or performance change.
  • Stop words: the classic check runs before the stop-word skip, so the^<39 nines> with a stop-word analyzer now gets ParseException instead of an empty query. The flexible parser still returns MatchNoDocsQuery("") there, because its processors drop the boost node before the build phase. Moving the classic check under if (q != null) keeps the old silent behaviour and matches the flexible parser.
  • The flexible message carries the float (Syntax Error: boost out of range: Infinity), because the node keeps only the value. A check in the Boost production (StandardSyntaxParser.jj:701, like FuzzyOp) could quote the literal, but the generated parser would have to be regenerated.
  • The flexible check sits in the builder, so it also sees a non-finite boost from configuration rather than query text (a field boost set with StandardQueryParser.setFieldsBoost becomes a BoostQueryNode in BoostQueryNodeProcessor.java:54-57); an infinite one would then give QueryNodeException instead of IllegalArgumentException (by reading, not run).
  • The surround parser's separate gap is not covered.

What the checks validated

Gradle with JDK 25 in a scratch worktree of 645d4fe14a20 (removed afterwards), seed DEADBEEF:

  • the repro fails on the stock build and passes with the fix, assertions on and off;
  • :lucene:queryparser:test: 31 classes, 534 tests, 0 failures, 1 skipped (in TestQPHelper), incl. TestQueryParser 73, TestStandardQP 46, TestQPHelper 38, TestPrecedenceQueryParser 16, TestMultiFieldQueryParser 15, TestComplexPhraseQuery 11;
  • :lucene:queryparser:check -x test with the repro in place: BUILD SUCCESSFUL (formatting, ecj lint, forbidden APIs, the three javacc checksum checks);
  • outcomes: a^3 and a^<Float.MAX_VALUE> unchanged; a^<39 nines> gives ParseException: Cannot parse 'a^<39 nines>': Boost <39 nines> is out of range (classic) and QueryNodeException: Syntax Error: boost out of range: Infinity (flexible).

B. Fall back to "no boost", as handleBoost's comment says (validated as a classic-only sketch)

Set f = 1.0f when Float.parseFloat returns a non-finite value (and the same in the flexible parser).

Trade-offs

Never fails and matches the code's stated intent for an invalid number, but the user's boost is silently ignored, the flexible parser needs its own change, and the repro's assertion must become "parse returns a query".

What the checks validated

Classic only, as a shadowed class: a fix-agnostic variant of the test passes, and the ParseException test fails as expected. The module suite was not run.

C. Convert any IllegalArgumentException in parse() into the parse exception

Catch IllegalArgumentException in QueryParserBase.parse (and in the flexible QueryParserHelper.parse) and rethrow it as ParseException / QueryNodeException, as parse() already does for TooManyClauses.

Trade-offs

Also fixes the other escapes of the same kind (#7761 invalid regex, LFV-300 negative slop) in one change, but is broader: an IAE from a bug in a custom subclass or analyzer would also be reported as a syntax error. Not tried.

Downstream note

A service that answers ParseException with "bad query" and anything else with a server error turns this 41-character query into an internal error.

How it was found

Non-formal: in the 2026-10-22 study, Jazzer fuzzing of the classic, flexible and simple query parsers, with the rule that a parser may only throw its documented exception, hit a boost of many nines. A study verifier reproduced it from scratch, and the hunt cut it to a one-test repro.

The method in full153 words

Method: non-formal (the study's fuzzing baseline, then code reading and executed tests). The study's QueryParserFuzzer (Jazzer, about 470 k executions over three runs) checked that the classic, flexible and simple parsers throw only their documented exception and that parsed queries survive toString, equals, count and rewrite; it reported five query-parser findings, this one among them (a boost of nines that parses to Infinity). The finding was blind: the fuzzer's oracle did not name boosts.

A study verifier then wrote its own deterministic 11-method JUnit test from scratch (2 contract tests fail, 9 characterisation tests pass), found the exact threshold, the other parsers and boost sites and the stop-word mask, and ran it across versions (7.7.3 builds (f:lucene)^Infinity; 8.0.0 to main throw). The 2026-10-29 noise audit replaced that class with the one-test repro, and on 2026-11-01 a reproducer and a tie-breaker re-ran it on main and on the release jars.

Prior reports, history and checks

Prior reports

No prior report of this trigger in GitHub issues and PRs (incl. the JIRA archive), commits, the mailing lists, downstream projects or the web (searched 2026-10-22 and 2026-11-01). It is the same kind of failure as the open #7761, where an invalid regex escapes parse().

Searches4 searches

Closest items, none a report of this bug:

  • #7761 (LUCENE-6703, open since 2015, 0 comments): an invalid regex /x)/ lets IllegalArgumentException escape QueryParserBase.parse(); same kind of failure, different trigger and fix site.
  • #9064 (LUCENE-8016, open, 0 comments): Adrien Grand proposes bounding boosts because boosts "too close to the float max value" give infinite scores; about scoring, not the parsers.
  • #2025 (LUCENE-950, closed 2010): the precedent above. #9044 (LUCENE-7996): the introducing change; its 19 comments mention negative boosts only.
  • A 2023 Elastic forum post: a client's double.MaxValue boost produced "boost must be a positive float, got Infinity" through Elasticsearch's JSON query DSL, not Lucene's parsers; not a Lucene report.
  • The OSS-Fuzz QueryParserFuzzer wraps the parsers in catch (RuntimeException e) {}, so it never reports an IAE out of parse().
  • #16820 (open): a maintainer (romseygeek) proposes, for Lucene 11, deprecating the xml, ext, complexPhrase, surround and flexible parsers; ComplexPhraseQueryParser and PrecedenceQueryParser are among them.

Limits: GitHub search reads at most 50 results per query and matches tokens, not phrases; private reports are not visible.

What was searched, when, and what turned up
WhereWhenHowFound
apache/lucene issues, PRs (incl. the migrated JIRA issues) and commits2026-10-22 (study) and 2026-11-01the study's searches; a scripted prior-report search (48 issue/PR and 5 commit queries, 8 JIRA queries); 17 manual GitHub searches; the tie-breaker's own searches: "boost must be a positive float", "got Infinity", boost overflow, huge boost, handleBoost, BoostQueryNodeBuilder, QueryParser IllegalArgumentException, OSS-Fuzz, jazzerno report of this trigger; closest #7761, #9064, #2025, #9044
Open PRs2026-11-01the changed files of all 410 open PRsnone touches the fix sites; #16578 touches BoostQuery.java only to add isKnownToMatchAllDocs
Mailing lists, downstream projects, web2026-11-0132 scripted and 35 manual queries over dev@, java-user@, issues@, commits@ and the Solr lists; Elasticsearch, OpenSearch, Solr and lucenenet; 19 web queriesno report; one downstream forum post via another entry point
Upstream main2026-11-01commits on the affected files after 645d4fe14a20, up to 2f424fd428da (2026-10-30)unchanged; the only queryparser commit since 2026-10-01 (#16446) is already in 645d4fe14a20

History

Introduced in 8.0.0 by LUCENE-7996 (a8a63464e7da, 2017-12-06), which added the finite check to BoostQuery without touching the parsers. All 61 releases from 8.0.0 to 10.6.0 and main fail; 7.0.0-7.7.3 instead return (f:lucene)^Infinity. Not fixed upstream.

Versions tested5 rows

Introduced by LUCENE-7996 "Queries are now required to produce positive scores" (#9044, by Adrien Grand, @jpountz; JIRA patch era, no PR), commit a8a63464e7da, 2017-12-06. It added three lines to the BoostQuery constructor and changed no query parser file; at the 7.7.3 and 8.0.0 tags every parser file on the boost path is byte-identical. The overflow itself is older (both parsers convert the boost with Float.valueOf up to 6.4.2 and with Float.parseFloat from 6.5.0 on, after LUCENE-7739 e7b87f5b79f; both return Infinity); only the exception is new. A shadow pair confirms it: 8.0.0 with only those three lines removed returns (f:lucene)^Infinity again, and 7.7.3 with only those lines added throws like 8.0.0. Upstream main 2f424fd428da (2026-10-30) has the same code.

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
10.6.0, 10.5.2 affectedminimal repro fails with assertions on and off (release jars, checksums verified)
8.0.0 to 10.6.0, all 61 releases first affected 8.0.0history test fails for both parsers: IllegalArgumentException escapes parse()
7.0.0 to 7.7.3, all 14 releases silent Infinityboth parsers return (f:lucene)^Infinity, no exception
8.0.0 without the 3 BoostQuery lines / 7.7.3 with them shadow pair8.0.0 passes again; 7.7.3 fails like 8.0.0

Independent checks

Confirmed real, with high confidence on the facts, by a study verifier, a reproducer and a tie-breaker, all from executed runs; a noise audit kept it as Low. A cross-model review was started but produced no verdict.

Checks and verdicts213 words

Everyone agrees on the root cause and that only the exception type is wrong.

Each independent check and its verdict
CheckVerdictWhat it found
study verifier new bug, lowIts own 11-method JUnit (2 contract tests fail) and a cross-version run (7.7.3 silent Infinity, 8.0.0 to main throw); exact threshold, other parsers and sites, stop-word mask.
noise audit questioned, then confirmed as LowTriage: absurd parameter, loud failure. Challenger: the input is end-user query text and the loud failure is the contract violation; replaced the 11-method class with the one-test repro.
reproducer realThe minimal test fails on main and 10.5.2 with assertions on and off and passes with the classic fix shadowed; the study test re-run gives 11 run, 2 failed.
tie-breaker real, high confidenceRe-ran everything on stock jars incl. 10.6.0 and a framework-free probe on 9.12.3, 8.0.0 and 7.7.3; sketched both fix sites; checked five refutation angles; corrected the LUCENE-950 precedent wording.
history introducing change confirmedSweep of all 75 releases from 7.0.0 to 10.6.0 and a shadow pair on 7.7.3 / 8.0.0.
cross-model review (OpenAI GPT-5.6, refute lens) no verdictStarted read-only and stopped while still reading the code, which it read the same way as the finder; it gave no verdict.

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 notes559 words

Finding record

LFV-028, snapshot of 2026-11-06

Registry title
A boost too large for a float parses to Infinity and both query parsers throw IllegalArgumentException
Area
queryparser / classic and flexible
Priority
Low, rank 1053: A 39-digit boost literal overflows to Infinity and both query parsers let IllegalArgumentException escape instead of ParseException, an input only fuzzers or hostile queries produce.
Impact class
wrong error
Impact note
Applications that parse untrusted query strings with the classic or flexible parser; one input shape; released since 8.0.0.
Summary
You parse a query whose boost literal is too large for a float (2^128 - 2^103 or more, which takes 39 digits) with the classic or flexible query parser. You expect a parse error or a usable boost. Instead the boost parses to Infinity and BoostQuery's finite check throws IllegalArgumentException out of parse().
Introduced
LUCENE-7996 (a8a63464e7da) added the isFinite check to BoostQuery (2017-12-06)
First affected version
8.0.0
Affected versions
every release from 8.0.0 to 10.6.0 (61 releases) and main 645d4fe14a20 [exec]; 7.0.0-7.7.3 return (f:lucene)^Infinity instead [exec]
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed in the earlier formal verification study by independent AI checks and a rerun on Lucene main; not yet triaged by a person.
Found by
non-formal baseline: fuzzing/PBT
Upstream and prior reports
none found (study searches 2026-10-22; a prior-report search on 2026-11-01, read-only, over GitHub issues and PRs including the migrated JIRA archive, commits, the mailing lists, downstream projects and the web, plus the changed files of all 410 open PRs; closest, not this bug: #7761 (LUCENE-6703, open), an invalid regex that lets IllegalArgumentException escape parse(), the same kind of failure with a different trigger and fix site)
Lucene commit tested
main 645d4fe14a20 (the minimal repro with assertions on and off, standalone and through gradle; fix A in a scratch worktree: the repro, all 534 tests of the queryparser module and :lucene:queryparser:check); the 10.6.0 and 10.5.2 release jars (checksums verified against Maven Central; the minimal repro with assertions on and off); a test-framework-free probe on main, 10.6.0, 9.12.3, 8.0.0 and 7.7.3; a fix-agnostic history test on every release from 7.0.0 to 10.6.0. Upstream main 2f424fd428da (2026-10-30) has the same code on the boost path (read).

Lucene and upstream sources

The finding in 30 seconds
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParser.jj:218, :313, :372 (at 645d4fe14a20)
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java:127-146, :786-795 (:791), :813-831 (at 645d4fe14a20)
  • lucene/core/src/java/org/apache/lucene/search/BoostQuery.java:34-44 (at 645d4fe14a20)
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/parser/StandardSyntaxParser.jj:115-116, :160, :701, :716-724 (at 645d4fe14a20)
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/BoostQueryNodeBuilder.java:48 (at 645d4fe14a20)
Minimal repro
  • lucene/MIGRATE.md:1418 (LUCENE-7444, StandardAnalyzer stop words) (at 645d4fe14a20)
Why it looks unintended
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java:127, :140-144, :821-823 (at 645d4fe14a20)
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/StandardQueryParser.java:274; flexible/core/QueryParserHelper.java:211 (at 645d4fe14a20)
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/package-info.java:268-269 (at 645d4fe14a20)
  • lucene/queryparser/src/test/org/apache/lucene/queryparser/util/QueryParserTestBase.java:976-989 (at 645d4fe14a20)
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/core/nodes/BoostQueryNode.java:29 (at 645d4fe14a20)
  • lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java:991; classic/TestMultiFieldQueryParser.java:210-297; flexible/standard/TestMultiFieldQPHelper.java:205-293; classic/MultiFieldQueryParser.java:332, :379, :428 (at 645d4fe14a20)
Suggested fix
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/BoostQueryNodeProcessor.java:54-57; flexible/standard/StandardQueryParser.java:526 (setFieldsBoost) (at 645d4fe14a20)
Prior reports
  • https://github.com/apache/lucene/issues/16820 (read-only GET, 2026-11-01: "Let's deprecate xml, ext, complexPhrase, surround and flexible.")
History
  • a Lucene clone: git show releases/lucene-solr/6.4.2 and 6.5.0 QueryParserBase.java (:904 / :889) and StandardSyntaxParser.jj (:394); git show e7b87f5b79f

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

Repro files

About the diagrams

Made in parallel with this doc from the code at 645d4fe14a20 and the repro's input and output.

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.