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
- Inspect the caller and fix the arithmetic or initialization so stageNumber is >= 0
- Validate stageNumber before constructing StageId, or clamp/reject negative values at the input boundary
- 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
- Never compute stage numbers by subtraction without bounds checks
- Initialize stage counters to 0, not -1
- Validate stage numbers at deserialization boundaries
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
- Not a valid stage id: [%s]
- No such stage[%s]
- Only positive integers or zero can be added
- Cannot have null or empty column name
- Must have at least one input channel
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/62d83223fcd09539.
Report an issue: GitHub.