Query parsers
|
This section documents the current Apache Lucene 10.x line — Lucene 10 requires Java 21 — as published
at the Apache Lucene documentation and Javadoc, which is the reference these
pages are written and verified against. No specific patch version is pinned; examples target This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Lucene iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
A query parser turns a user- or application-supplied string into a
Query tree. Lucene ships several, in the
lucene-queryparser module, trading expressiveness against robustness against free-text input. This
page covers what each parser accepts, the traps in the classic one, and why programmatic code should
usually build Query objects directly. See
the
lucene-queryparser package summary.
The classic QueryParser
org.apache.lucene.queryparser.classic.QueryParser implements the familiar Lucene query-string
grammar:
term field:term "a phrase"
+required -prohibited AND OR NOT (operators are case-sensitive, uppercase)
grouping: (quick OR fast) AND fox
wildcards: te?t test* (leading ? / * rejected by default)
fuzzy: roam~ roam~1 (max 2 edits)
proximity: "lucene search"~4
boost: lucene^4 "phrase query"^2
inclusive range: mod_date:[20200101 TO 20201231]
exclusive range: count:{1 TO 5}
regex: name:/joh?n(ath[oa]n)/
// https://lucene.apache.org/core/10_0_0/queryparser/org/apache/lucene/queryparser/classic/QueryParser.html
Analyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser("text", analyzer);
parser.setDefaultOperator(QueryParser.Operator.AND); // default is OR
parser.setAllowLeadingWildcard(false); // keep it false in production
Query q = parser.parse("quick brown +fox -sports");
Pitfalls
-
Query text is analyzed. Each term (except wildcard, fuzzy, regex and range terms) is run through the field’s analyzer, so the parser’s field must map to the same analyzer used at index time. A term that the analyzer splits or drops (a stop word, a hyphenated token) produces a sub-query you did not type, or no clause at all.
-
Wildcard, prefix, fuzzy and regex terms skip the analyzer — they are lower-cased only if you leave
setLowercaseExpandedTerms-style behaviour to the analyzer yourself.Fox*will not match an index of lower-casedfoxunless you normalise the pattern first. -
Leading wildcards are disabled because
*foomust enumerate the whole term dictionary. Turning them on withsetAllowLeadingWildcard(true)can be very slow on a large field. -
Ranges build a
TermRangeQuery, which does a byte-ordered string comparison. Over a numericIntPoint/LongPoint/DoublePointfield that is simply wrong —[10 TO 9]sorts "correctly" as strings. For numeric or date ranges either post-process the parsed range, subclassgetRangeQuery(…)to emitIntPoint.newRangeQuery(…), or do not parse the range at all. -
Boolean operators are not real precedence.
a AND b OR cis not(a AND b) OR c; internally the parser rewritesAND/OR/NOTinto`/`-` clause modifiers, and mixing them with explicit `/-in one query is a common source of surprise. Group explicitly with parentheses. -
MultiFieldQueryParserfans each term out across several fields as aSHOULDdisjunction; it is convenient but produces large boolean queries and does not deduplicate scoring the wayDisjunctionMaxQuerydoes.
// https://lucene.apache.org/core/10_0_0/queryparser/org/apache/lucene/queryparser/classic/MultiFieldQueryParser.html
Map<String, Float> boosts = Map.of("title", 3.0f, "body", 1.0f);
QueryParser mf = new MultiFieldQueryParser(new String[] {"title", "body"}, analyzer, boosts);
Query q = mf.parse("lucene query parser");
SimpleQueryParser — never throws
org.apache.lucene.queryparser.simple.SimpleQueryParser is built for raw end-user input: it never
raises a parse exception. Unbalanced quotes, a trailing operator, a stray bracket — all are treated
as literal text. Operators are punctuation (+ and, | or, - not, " phrase, * prefix, ~N
fuzzy/slop, ( ) grouping) and each can be individually disabled with a flags bitmask.
// https://lucene.apache.org/core/10_0_0/queryparser/org/apache/lucene/queryparser/simple/SimpleQueryParser.html
Map<String, Float> weights = Map.of("title", 2.0f, "body", 1.0f);
SimpleQueryParser sqp = new SimpleQueryParser(analyzer, weights);
sqp.setDefaultOperator(BooleanClause.Occur.MUST); // space means AND
Query q = sqp.parse("lucene +search -legacy \"exact phrase\" quick~2");
// A version disabling prefix and fuzzy operators:
SimpleQueryParser restricted = new SimpleQueryParser(analyzer, weights,
SimpleQueryParser.AND_OPERATOR | SimpleQueryParser.PHRASE_OPERATOR);
StandardQueryParser — the flexible framework
org.apache.lucene.queryparser.flexible.standard.StandardQueryParser accepts the same syntax as the
classic parser but is built on the flexible query parser framework: a pipeline of syntax parser →
QueryNode tree → configurable processor pipeline → builder. You subclass or reconfigure one stage
instead of overriding getXxxQuery methods, which makes it the right base for a house query language
or for changing how ranges/points are built globally.
// https://lucene.apache.org/core/10_0_0/queryparser/org/apache/lucene/queryparser/flexible/standard/StandardQueryParser.html
StandardQueryParser sp = new StandardQueryParser(analyzer);
sp.setDefaultOperator(StandardQueryConfigHandler.Operator.AND);
sp.setAllowLeadingWildcard(false);
// Point-typed numeric ranges: register a PointsConfig per numeric field.
Map<String, PointsConfig> points = Map.of(
"price", new PointsConfig(new DecimalFormat(), Integer.class));
sp.setPointsConfigMap(points);
Query q = sp.parse("title:lucene AND price:[10 TO 100]", "body");
ComplexPhraseQueryParser
org.apache.lucene.queryparser.complexPhrase.ComplexPhraseQueryParser allows wildcard, prefix, fuzzy
and range sub-clauses inside a quoted phrase, which the classic parser forbids — useful for
"these alternatives, in order, close together".
// https://lucene.apache.org/core/10_0_0/queryparser/org/apache/lucene/queryparser/complexPhrase/ComplexPhraseQueryParser.html
ComplexPhraseQueryParser cp = new ComplexPhraseQueryParser("text", analyzer);
cp.setInOrder(true);
Query q = cp.parse("\"(john jon jonathan) smith*\"~3");
Just build the Query
For anything programmatic — a REST filter, a saved search, a facet drill-down — skip parsing.
Constructing Query objects directly removes the analyzer
round-trip surprises, is not vulnerable to syntax injection from user data, and lets the compiler
check the query shape. Reserve the parsers for text a human actually typed into a search box.
From string to Query tree
Assuming field text uses StandardAnalyzer (lower-cases, splits on whitespace/punctuation, keeps
stop words in modern defaults) and the classic QueryParser with default operator OR:
| User string | After analysis | Resulting Query |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
not analyzed |
|
|
not analyzed |
|
|
not analyzed |
|
|
(empty) |
no clause → |
Related pages
-
The core Query classes — what the parsers build, and the API to build directly.
-
Analysis pipeline — why the parser’s analyzer must match the index analyzer.
-
Points & range queries — the correct query type for numeric and date ranges.
-
Interval & span queries — positional matching beyond phrase slop.
-
Solr: query parsers — the same classic grammar plus DisMax/eDisMax and local params on top.