apache/druid · error · IllegalArgumentException (IAE)

'skip' must be greater than zero

Error message

'skip' must be greater than zero

What it means

ScanQueryOffsetSequence wraps a base scan sequence to skip the first N result rows (query offset). A skip value below 1 is meaningless (offset must be at least 1 to have an effect), so the constructor throws IAE immediately.

Source

Thrown at processing/src/main/java/org/apache/druid/query/scan/ScanQueryOffsetSequence.java:48

import java.util.List;
import java.util.stream.Collectors;

/**
 * A Sequence that wraps the results of a ScanQuery and skips a given number of rows. It is used to implement
 * the "offset" feature.
 */
public class ScanQueryOffsetSequence extends YieldingSequenceBase<ScanResultValue>
{
  private final Sequence<ScanResultValue> baseSequence;
  private final long skip;

  public ScanQueryOffsetSequence(Sequence<ScanResultValue> baseSequence, long skip)
  {
    this.baseSequence = baseSequence;
    this.skip = skip;

    if (skip < 1) {
      throw new IAE("'skip' must be greater than zero");
    }
  }

  @Override
  public <OutType> Yielder<OutType> toYielder(
      final OutType initValue,
      final YieldingAccumulator<OutType, ScanResultValue> accumulator
  )
  {
    final SkippingYieldingAccumulator<OutType> skippingAccumulator = new SkippingYieldingAccumulator<>(accumulator);
    return wrapYielder(baseSequence.toYielder(initValue, skippingAccumulator), skippingAccumulator);
  }

  private <OutType> Yielder<OutType> wrapYielder(
      final Yielder<OutType> yielder,
      final SkippingYieldingAccumulator<OutType> accumulator
  )
  {

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Only wrap with ScanQueryOffsetSequence when query.getScanRowsOffset() >= 1
  2. Fix client-supplied offset values to be at least 1 (or omit offset)
  3. Guard construction: if (offset > 0) apply offset sequence, else use base sequence

Example fix

// before
Sequence<ScanResultValue> seq = new ScanQueryOffsetSequence(base, query.getScanRowsOffset()); // offset may be 0
// after
final long offset = query.getScanRowsOffset();
Sequence<ScanResultValue> seq = offset >= 1 ? new ScanQueryOffsetSequence(base, offset) : base;
Defensive patterns

Strategy: validation

Validate before calling

long offset = query.getScanRowsOffset();
if (offset < 1) { /* do not construct ScanQueryOffsetSequence */ }

Type guard

boolean offsetApplicable(ScanQuery q) { return q.getScanRowsOffset() >= 1; }

Try / catch

try {
  seq = new ScanQueryOffsetSequence(base, skip);
} catch (IllegalArgumentException e) {
  seq = base; // skip<=0 means no offset needed
}

Prevention

When it happens

Trigger: Constructing ScanQueryOffsetSequence with skip = 0 or negative, typically from a ScanQuery whose getScanRowsOffset() is 0 or negative (e.g. context 'offset':0 applied via mergeResults).

Common situations: Programmatic query building where offset defaults to 0 and the offset sequence is applied unconditionally; manual API misuse; clients sending negative offset values in query context.

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/6ade58f9c7b0de29. Report an issue: GitHub.