apache/druid · error · IllegalStateException
Null cursor factory found. Probably trying to issue a query
Error message
Null cursor factory found. Probably trying to issue a query against a segment being memory unmapped.
What it means
Inside the timeboundary runner's IteratorMaker.make(), the segment's cursor factory is checked for null before computing min/max times; a null cursor factory means the segment's underlying storage has already been released. Druid throws this IllegalStateException because querying a segment whose memory has been unmapped (e.g. a swappable/mmapped segment evicted while a query is in flight) cannot proceed safely.
Source
Thrown at processing/src/main/java/org/apache/druid/query/timeboundary/TimeBoundaryQueryRunnerFactory.java:129
final QueryPlus<Result<TimeBoundaryResultValue>> queryPlus,
final ResponseContext responseContext
)
{
Query<Result<TimeBoundaryResultValue>> input = queryPlus.getQuery();
if (!(input instanceof TimeBoundaryQuery)) {
throw new ISE("Got a [%s] which isn't a %s", input.getClass(), TimeBoundaryQuery.class);
}
final TimeBoundaryQuery query = (TimeBoundaryQuery) input;
return new BaseSequence<>(
new BaseSequence.IteratorMaker<>()
{
@Override
public Iterator<Result<TimeBoundaryResultValue>> make()
{
if (cursorFactory == null) {
throw new ISE(
"Null cursor factory found. Probably trying to issue a query against a segment being memory unmapped."
);
}
DateTime minTime = null;
DateTime maxTime = null;
if (canUseTimeBoundaryInspector(query, timeBoundaryInspector)) {
if (query.needsMinTime()) {
minTime = timeBoundaryInspector.getMinTime();
}
if (query.needsMaxTime()) {
maxTime = timeBoundaryInspector.getMaxTime();
}
} else {
final Pair<DateTime, DateTime> timeBoundary = getTimeBoundary(query, cursorFactory);
minTime = timeBoundary.left();View on GitHub (pinned to 9b90983fd2)
Solutions
- Retry the query — the broker will typically route to a server still serving the segment
- Check historical node logs/memory to ensure segments aren't being dropped mid-query (review load/drop rules and kill tasks)
- Increase memory or reduce segment eviction pressure on historicals
- Ensure query timeouts and retries are configured so transient unmapping races are retried rather than surfaced to users
Example fix
// before
// no retry; a dropped mid-flight segment surfaces ISE to the user
List<Result<TimeBoundaryResultValue>> r = queryRunner.run(QueryPlus.wrap(tbQuery), ctx).toList();
// after
try {
r = queryRunner.run(QueryPlus.wrap(tbQuery), ctx).toList();
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().contains("Null cursor factory")) {
r = retryQuery(tbQuery); // broker re-routes to a live segment
} else {
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
// Before issuing: check the segment is still served by this server
if (segment == null || segment.asStorageAdapter() == null) {
throw new IllegalStateException("Segment no longer available for query");
} Type guard
boolean isSegmentQueryable(Segment segment) {
try {
return segment != null && segment.asStorageAdapter() != null;
} catch (Exception e) {
return false;
}
} Try / catch
try {
return runner.run(QueryPlus.wrap(query), ctx).toList();
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().contains("Null cursor factory")) {
return retryWithBackoff(query, 3); // broker will re-route to a live segment
}
throw e;
} Prevention
- Configure query retries/timeouts so broker re-routes on transient segment unavailability
- Review drop rules and kill tasks to avoid unloading segments under active query load
- Monitor historical memory pressure that triggers aggressive segment unmapping
- Prefer broker-mediated queries over direct historical queries to get automatic re-routing
When it happens
Trigger: Issuing a timeBoundary query against a segment whose CursorFactory is null, which happens when the segment's data is memory-unmapped (unloaded/evicted) between runner construction and iterator creation — typically on historical nodes with aggressive segment drop or during realtime hand-off.
Common situations: Segments dropped or swapped out while a query runs (e.g. kill tasks, load rules dropping data, broker retries landing on a historical that just unloaded the segment); low-memory historicals evicting mmapped segments; race between a query in flight and segment cleanup.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Null cursor factory found. Probably trying to issue a query
- Null cursor factory found. Probably trying to issue a query
- Emit called unexpectedly before service start
- unknown event type [%s]
- interrupted flushing elements from queue
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/e0ee242a78737ab5.
Report an issue: GitHub.