The grammar accepts a boost of any length, such as 39 nines, but that is too big for a float.
- In the classic and flexible query syntax,
term^Nmultiplies a clause's score by N:apache^4makes matches onapachecount four times as much. N is plain digits with an optional fraction: no sign, no exponent, no length limit. - Each parser turns N into a Java
floatwithFloat.parseFloatand wraps the clause in aBoostQuery. Since 8.0.0,BoostQueryrejects a negative, NaN or infinite boost withIllegalArgumentException.
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.- Parse
a^3with the classicQueryParser: you get(f:a)^3.0. Evena^340282346638528859811704183484516925440, the decimal value ofFloat.MAX_VALUE, works:(f:a)^3.4028235E38. - Now parse
a^999999999999999999999999999999999999999(39 nines, about 1.0e39). You expectParseException, the only exceptionparse()declares, as you get for the same number after~. - Instead
Float.parseFloatrounds the boost toInfinitywithout throwing,new BoostQuery(q, Infinity)rejects it, andIllegalArgumentException: boost must be a positive float, got Infinityescapesparse(). - The flexible
StandardQueryParserfails the same way instead of throwingQueryNodeException. Every boost literal from 2^128 - 2^103 (39 digits) up overflows, at every boost site, in every release since 8.0.0.
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.
- Classic parser:
org.apache.lucene.queryparser.classic.QueryParser(and its subclassesMultiFieldQueryParser,ComplexPhraseQueryParser);parse(String)documents@throws ParseException if the parsing fails. - Flexible parser:
StandardQueryParser(andPrecedenceQueryParser), 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 toInfinity; one less rounds toFloat.MAX_VALUE.
- The boost token is
<NUMBER: (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? >in both grammars (QueryParser.jj:218,StandardSyntaxParser.jj:160). - Classic
handleBoost(QueryParserBase.java:813-831) doesf = Float.parseFloat(boost.image)(:817) inside a catch whose comment says an invalid boost "defaults to no boost" (:821-823). On overflowFloat.parseFloatreturnsInfinityand does not throw, so that fallback never runs, andq = new BoostQuery(q, f)(:828) throws. 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);.parse()(QueryParserBase.java:129-146) converts onlyParseException | TokenMgrErrorandTooManyClausesintoParseException, so theIllegalArgumentExceptionleavesparse()unwrapped.- Flexible: the
Boostproduction buildsnew BoostQueryNode(node, parseFloat(boost))with no check (StandardSyntaxParser.jj:701), andBoostQueryNodeBuilder.build(:48) callsnew BoostQuery(...)withInfinity. The neighbouringFuzzyOpproduction validates its number and throwsParseException(StandardSyntaxParser.jj:716-724), as classichandleBareFuzzydoes (QueryParserBase.java:786-795).
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,StandardQueryParserorPrecedenceQueryParserthat handle bad input withcatch (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
BoostQueryenforced that, ordinary literals such as^2000would 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, inSrndQuery.makeLuceneQueryField. Other unchecked exceptions escapeparse()for other inputs: an invalid regex (#7761) and a negative phrase slop (LFV-300).
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.
/*
* 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)));
}
}
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 exceptionparse()declares.- Observed
IllegalArgumentException: boost must be a positive float, got InfinityfromBoostQuery.<init>viaQueryParserBase.handleBoost:828; the same with assertions off.
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.
| lucene | main 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 |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux, aarch64 |
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)
# 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)parse()documents only@throws ParseException(flexible:QueryNodeException) and already converts the runtimeTooManyClausesintoParseException.- The same 39-digit number after
~gets the declared exception in both parsers, andhandleBoost's own comment says an invalid boost means "no boost". - LUCENE-7996 added the finite check to
BoostQueryfor 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:274andQueryParserHelper.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 flexibleBoostQueryNodejavadoc says "The boost value may vary from 0.0 to 1.0" (BoostQueryNode.java:29), a stale range that nothing enforces: flexiblea^3builds(f:a)^3.0. TheBoostQueryconstructor javadoc has no@throws. - Tests. Upstream
QueryParserTestBase.testBooleanQuery(:985-989) asserts thatTooManyClausescomes out as a parse exception, andtestException(:976-983) asserts parse exceptions for malformed text. No test expects anIllegalArgumentExceptionfrom parsing query text; the module's IAE tests cover parser setters and the staticMultiFieldQueryParser.parse(String[], String[], ...)overloads, which document it for arrays of different lengths. - History. LUCENE-7996 (#9044,
a8a63464e7da, 2017-12-06) touched 22 files, none underlucene/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'sIllegalArgumentExceptionescaped classicparse()forfoo~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 madefoo~1legal. Not a ruling thatparse()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.
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.
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_VALUEare 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 getsParseExceptioninstead of an empty query. The flexible parser still returnsMatchNoDocsQuery("")there, because its processors drop the boost node before the build phase. Moving the classic check underif (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 theBoostproduction (StandardSyntaxParser.jj:701, likeFuzzyOp) 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.setFieldsBoostbecomes aBoostQueryNodeinBoostQueryNodeProcessor.java:54-57); an infinite one would then giveQueryNodeExceptioninstead ofIllegalArgumentException(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 (inTestQPHelper), incl.TestQueryParser73,TestStandardQP46,TestQPHelper38,TestPrecedenceQueryParser16,TestMultiFieldQueryParser15,TestComplexPhraseQuery11;:lucene:queryparser:check -x testwith the repro in place: BUILD SUCCESSFUL (formatting, ecj lint, forbidden APIs, the three javacc checksum checks);- outcomes:
a^3anda^<Float.MAX_VALUE>unchanged;a^<39 nines>givesParseException: Cannot parse 'a^<39 nines>': Boost <39 nines> is out of range(classic) andQueryNodeException: Syntax Error: boost out of range: Infinity(flexible).
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.
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.
A service that answers ParseException with "bad query" and anything else with a server error turns this 41-character query into an internal error.
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.
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)/letsIllegalArgumentExceptionescapeQueryParserBase.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.MaxValueboost 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
QueryParserFuzzerwraps the parsers incatch (RuntimeException e) {}, so it never reports an IAE out ofparse(). - #16820 (open): a maintainer (romseygeek) proposes, for Lucene 11, deprecating the xml, ext, complexPhrase, surround and flexible parsers;
ComplexPhraseQueryParserandPrecedenceQueryParserare among them.
Limits: GitHub search reads at most 50 results per query and matches tokens, not phrases; private reports are not visible.
| Where | When | How | Found |
|---|---|---|---|
| apache/lucene issues, PRs (incl. the migrated JIRA issues) and commits | 2026-10-22 (study) and 2026-11-01 | the 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, jazzer | no report of this trigger; closest #7761, #9064, #2025, #9044 |
| Open PRs | 2026-11-01 | the changed files of all 410 open PRs | none touches the fix sites; #16578 touches BoostQuery.java only to add isKnownToMatchAllDocs |
| Mailing lists, downstream projects, web | 2026-11-01 | 32 scripted and 35 manual queries over dev@, java-user@, issues@, commits@ and the Solr lists; Elasticsearch, OpenSearch, Solr and lucenenet; 19 web queries | no report; one downstream forum post via another entry point |
| Upstream main | 2026-11-01 | commits 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 |
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.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | minimal repro fails with assertions on and off (standalone and gradle); passes with fix A |
| 10.6.0, 10.5.2 | affected | minimal 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.0 | history test fails for both parsers: IllegalArgumentException escapes parse() |
| 7.0.0 to 7.7.3, all 14 releases | silent Infinity | both parsers return (f:lucene)^Infinity, no exception |
| 8.0.0 without the 3 BoostQuery lines / 7.7.3 with them | shadow pair | 8.0.0 passes again; 7.7.3 fails like 8.0.0 |
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.
| Check | Verdict | What it found |
|---|---|---|
| study verifier | new bug, low | Its 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 Low | Triage: 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 | real | The 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 confidence | Re-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 confirmed | Sweep 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 verdict | Started read-only and stopped while still reading the code, which it read the same way as the finder; it gave no verdict. |
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 407 KB
Finding record, Lucene and upstream sources, repro files and diagram notes559 words
- 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 main2f424fd428da(2026-10-30) has the same code on the boost path (read).
- 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
- TestHugeBoostParseException.java (JUnit, one test):
repro/LFV-028/src/org/apache/lucene/queryparser/classic/TestHugeBoostParseException.java(29 lines)
Made in parallel with this doc from the code at 645d4fe14a20 and the repro's input and output.
assets/LFV-028/bug.png(407 KB)
