apache/druid · error · IllegalArgumentException

Should only have one interval, got

Error message

Should only have one interval, got[%s]

What it means

GroupBy processing requires the query's querySegmentSpec to contain exactly one interval. validateForProcess throws this IAE when the interval list is empty or has multiple intervals, since the cursor-holder based processing path can only iterate a single interval.

Solutions

  1. Issue one query per interval, or use a single covering interval
  2. Use multipleIntervals handling at the tool/SQL layer (Druid SQL splits ranges) instead of the native groupBy v2 cursor path
  3. Validate query.getQuerySegmentSpec().getIntervals().size() == 1 before processing
  4. Rewrite with a union datasource of single-interval subqueries if multiple ranges are needed

Example fix

// before
query.setInterval(Arrays.asList("2022-01-01/2022-02-01", "2022-03-01/2022-04-01"));
// after
query.setInterval("2022-01-01/2022-04-01"); // single covering interval, or split into two queries
Defensive patterns

Strategy: validation

Validate before calling

List<Interval> intervals = query.getQuerySegmentSpec().getIntervals();
if (intervals.size() != 1) {
  throw new IAE("groupBy cursor path needs exactly one interval, got %d", intervals.size());
}

Try / catch

try { engine.process(query, ...); } catch (IAE e) { if (e.getMessage().startsWith("Should only have one interval")) { splitAndRunPerInterval(query); } else { throw e; } }

Prevention

When it happens

Trigger: Issuing a GroupByQuery (via GroupingEngine.process/makeCursorHolderAsync/processCursorHolder) whose getQuerySegmentSpec().getIntervals() returns a list with size != 1 — e.g. multiple explicit intervals like ["2012-01-01/2012-01-02","2012-02-01/2012-02-02"] or none.

Common situations: Programmatically building a query with multiple intervals; client SDKs accepting interval arrays; migrating code from the legacy engine that accepted multiple intervals; converting SQL with disjoined time ranges.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/d71fc46ae4f358be. Report an issue: GitHub.

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/query/groupby/GroupingEngine.java:537

      @Nullable GroupByQueryMetrics groupByQueryMetrics
  )
  {
    validateForProcess(query, cursorFactory);
    final CursorBuildSpec buildSpec = makeCursorBuildSpec(query, groupByQueryMetrics);
    return processWithCursorHolder(query, cursorFactory, cursorHolder, timeBoundaryInspector, bufferPool, buildSpec);
  }

  private static void validateForProcess(GroupByQuery query, @Nullable CursorFactory cursorFactory)
  {
    if (cursorFactory == null) {
      throw new ISE(
          "Null cursor factory found. Probably trying to issue a query against a segment being memory unmapped."
      );
    }

    final List<Interval> intervals = query.getQuerySegmentSpec().getIntervals();
    if (intervals.size() != 1) {
      throw new IAE("Should only have one interval, got[%s]", intervals);
    }
  }

  private Sequence<ResultRow> processWithCursorHolder(
      GroupByQuery query,
      CursorFactory cursorFactory,
      CursorHolder cursorHolder,
      @Nullable TimeBoundaryInspector timeBoundaryInspector,
      NonBlockingPool<ByteBuffer> bufferPool,
      CursorBuildSpec buildSpec
  )
  {
    // Register the cursor holder on the closer before any work that could throw, so a single catch path covers
    // every cleanup scenario (bufferPool.take() failure, pipeline construction failure, etc.) and the original
    // exception is preserved with any close errors as suppressed.
    final Closer closer = Closer.create();
    closer.register(cursorHolder);

View on GitHub (pinned to 9b90983fd2)