Interval & span queries

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.

Proximity matching — "these terms, near each other, in this order, but not when a third term sits between them" — is expressed in modern Lucene with the Intervals API: composable IntervalsSource factory methods wrapped in a single IntervalQuery. It replaces the older SpanQuery family with a smaller, nestable vocabulary that reads closer to the question being asked.

The Intervals / IntervalQuery API

org.apache.lucene.queries.intervals.Intervals is a factory of static methods that each build an IntervalsSource; IntervalQuery(field, source) turns one into a runnable Query scoped to a single analyzed field. Sources nest freely, so a complex proximity constraint is built up from small parts rather than a deep tree of span classes.

Factory Matches an interval that…​

Intervals.term("t")

is a single occurrence of term t

Intervals.phrase("a", "b")

is a immediately followed by b (adjacent, in order)

Intervals.ordered(a, b, c)

contains a, b, c in that order, any gaps between

Intervals.unordered(a, b)

contains both a and b, in any order

Intervals.maxgaps(n, src)

is src with at most n non-matching positions inside it

Intervals.containing(big, small)

is big and also wholly contains a match of small

Intervals.containedBy(small, big)

is small and is wholly inside a match of big

Intervals.notContaining(a, b)

is a that does NOT contain a match of b

Intervals.notContainedBy(a, b)

is a that is NOT inside a match of b

Intervals.overlapping(a, b)

is a whose span overlaps a match of b

Intervals.before(a, b) / Intervals.after(a, b)

is a that appears before / after a match of b

See the org.apache.lucene.queries.intervals package Javadoc for the full list (it also covers atLeast, wildcard, prefix, regexp, fixField, extend, and the IntervalFunction combinators). The module ships in lucene-queries:

<dependency>
  <groupId>org.apache.lucene</groupId>
  <artifactId>lucene-queries</artifactId>
  <version>10.0.0</version>
</dependency>

A worked proximity example

Find documents where "quick" and "fox" occur within three positions of each other, in that order, but only when "lazy" does not appear between them:

// https://lucene.apache.org/core/10_0_0/queries/org/apache/lucene/queries/intervals/Intervals.html
import org.apache.lucene.queries.intervals.Intervals;
import org.apache.lucene.queries.intervals.IntervalQuery;
import org.apache.lucene.queries.intervals.IntervalsSource;
import org.apache.lucene.search.Query;

IntervalsSource near =
    Intervals.maxgaps(3,
        Intervals.ordered(
            Intervals.term("quick"),
            Intervals.term("fox")));

IntervalsSource clean =
    Intervals.notContaining(near, Intervals.term("lazy"));

Query q = new IntervalQuery("body", clean);

Because the field is analyzed, pass already-analyzed term text (lowercased here to match a StandardAnalyzer index). Combine an IntervalQuery with ordinary clauses through BooleanQuery.Builder the same way as any other Query:

import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;

Query combined = new BooleanQuery.Builder()
    .add(new IntervalQuery("body", clean), Occur.MUST)
    .add(org.apache.lucene.document.IntPoint.newRangeQuery("year", 1990, 2000), Occur.FILTER)
    .build();

IntervalQuery scores with the active Similarity using the matched interval’s frequency and width, so tighter matches score higher without a custom scorer. Its Matches support also makes it work directly with the highlighters — see Highlighting, suggesters & more.

The legacy SpanQuery family

Before Intervals, positional matching used SpanQuery: SpanTermQuery, SpanNearQuery, SpanOrQuery, SpanNotQuery, SpanContainingQuery, SpanPositionRangeQuery, and FieldMaskingSpanQuery — see the org.apache.lucene.queries.spans package Javadoc. These classes still exist for backward compatibility and for a few edge cases the Intervals API does not cover, but new code should use Intervals — it is faster to evaluate, composes without the payload/position bookkeeping, and expresses the common "within N, not containing X" shapes directly.