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 lucene-core 10.x and the companion modules. Some areas (the Panama foreign-memory / Vector API internals, codec file-format internals, and the nightly benchmark harness) are linked, not documented in depth.

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-cased fox unless you normalise the pattern first.

  • Leading wildcards are disabled because *foo must enumerate the whole term dictionary. Turning them on with setAllowLeadingWildcard(true) can be very slow on a large field.

  • Ranges build a TermRangeQuery, which does a byte-ordered string comparison. Over a numeric IntPoint/LongPoint/DoublePoint field that is simply wrong — [10 TO 9] sorts "correctly" as strings. For numeric or date ranges either post-process the parsed range, subclass getRangeQuery(…​) to emit IntPoint.newRangeQuery(…​), or do not parse the range at all.

  • Boolean operators are not real precedence. a AND b OR c is not (a AND b) OR c; internally the parser rewrites AND/OR/NOT into `/`-` clause modifiers, and mixing them with explicit `/- in one query is a common source of surprise. Group explicitly with parentheses.

  • MultiFieldQueryParser fans each term out across several fields as a SHOULD disjunction; it is convenient but produces large boolean queries and does not deduplicate scoring the way DisjunctionMaxQuery does.

// 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

Quick Brown

quick, brown

text:quick text:brown (two SHOULD clauses)

+Quick brown

quick, brown

+text:quick text:brown

"Quick Brown Fox"~2

quick brown fox

PhraseQuery on text, slop 2

quick AND brown

quick, brown

+text:quick +text:brown

title:Quick brown

quick (title), brown (text)

title:quick text:brown

Qu?ck bro*

not analyzed

WildcardQuery(text:qu?ck) PrefixQuery(text:bro)

roam~1

not analyzed

FuzzyQuery(text:roam, maxEdits=1)

mod_date:[20200101 TO 20201231]

not analyzed

TermRangeQuery on mod_date (byte-ordered, not a point range)

the (a stop word, if the analyzer drops it)

(empty)

no clause → MatchNoDocsQuery