apache/druid · error · IllegalArgumentException

Invalid stageNumber [%s]

Error message

Invalid stageNumber [%s]

What it means

StageId's constructor validates that a stage number is non-negative, throwing IAE with 'Invalid stageNumber [%s]' when a negative value is passed. Stage numbers in MSQ queries are sequential indices starting at 0, so a negative stage number is always a caller bug. This fails fast before the StageId is used to key kernels or work orders.

Source

Thrown at multi-stage-query/src/main/java/org/apache/druid/msq/kernel/StageId.java:50

/**
 * Globally unique stage identifier: query ID plus stage number.
 *
 * Note: Versions till Druid 30 had a bug in the QueryKits which populated the {@link #queryId} field with random
 * UUIDs. Therefore, all usage of the field must be vetted instead of assuming that it will be the expected query id
 */
public class StageId implements Comparable<StageId>
{
  private static final Comparator<StageId> COMPARATOR =
      Comparator.comparing(StageId::getQueryId)
                .thenComparing(StageId::getStageNumber);

  private final String queryId;
  private final int stageNumber;

  public StageId(final String queryId, final int stageNumber)
  {
    if (stageNumber < 0) {
      throw new IAE("Invalid stageNumber [%s]", stageNumber);
    }

    this.queryId = IdUtils.validateId("queryId", queryId);
    this.stageNumber = stageNumber;
  }

  @JsonCreator
  public static StageId fromString(final String s)
  {
    final int lastUnderscore = s.lastIndexOf('_');

    if (lastUnderscore > 0 && lastUnderscore < s.length() - 1) {
      final Long stageNumber = GuavaUtils.tryParseLong(s.substring(lastUnderscore + 1));

      if (stageNumber != null && stageNumber >= 0 && stageNumber <= Integer.MAX_VALUE) {
        return new StageId(s.substring(0, lastUnderscore), stageNumber.intValue());
      }
    }

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Inspect the caller and fix the arithmetic or initialization so stageNumber is >= 0
  2. Validate stageNumber before constructing StageId, or clamp/reject negative values at the input boundary
  3. Log the full queryId and stage counter around the call to find where the negative value originates

Example fix

// before
StageId id = new StageId(queryId, stageNumber - 1);
// after
if (stageNumber - 1 < 0) { throw new IllegalArgumentException("Cannot decrement stage 0"); }
StageId id = new StageId(queryId, stageNumber - 1);
Defensive patterns

Strategy: validation

Validate before calling

if (stageNumber < 0) throw new IllegalArgumentException("stageNumber must be >= 0, got " + stageNumber);
StageId id = new StageId(queryId, stageNumber);

Type guard

boolean isValidStageNumber(int n) { return n >= 0 && n <= Integer.MAX_VALUE; }

Prevention

When it happens

Trigger: Calling new StageId(queryId, stageNumber) with a negative int, e.g. computing a stage index from an uninitialized counter, an off-by-one subtraction, or deserializing a payload with stageNumber < 0.

Common situations: Custom controllers or tooling that computes stage numbers arithmetically; plugins that offset stage indices (stageNumber - 1) before the first stage; JSON/config ingestion where the stage field defaulted to -1.

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/62d83223fcd09539. Report an issue: GitHub.