apache/druid · error · IllegalStateException
Cannot accept duplicate stage numbers
Error message
Cannot accept duplicate stage numbers
What it means
QueryDefinition.create builds the stage map from the supplied stage definitions and requires each stage to have a unique stage number. If two StageDefinitions share the same StageId/stage number within one query, the internal maps would silently overwrite each other, so create throws ISE on the duplicate.
Source
Thrown at multi-stage-query/src/main/java/org/apache/druid/msq/kernel/QueryDefinition.java:84
)
{
this.stageDefinitions = stageDefinitions;
this.finalStage = finalStage;
this.context = context;
}
@JsonCreator
public static QueryDefinition create(
@JsonProperty("stages") final List<StageDefinition> stageDefinitions,
@Nullable @JsonProperty("context") final QueryContext context)
{
final Map<StageId, StageDefinition> stageMap = new HashMap<>();
final Set<StageId> nonFinalStages = new HashSet<>();
final IntSet stageNumbers = new IntOpenHashSet();
for (final StageDefinition stage : stageDefinitions) {
if (!stageNumbers.add(stage.getStageNumber())) {
throw new ISE("Cannot accept duplicate stage numbers");
}
stageMap.put(stage.getId(), stage);
for (int stageNumber : stage.getInputStageNumbers()) {
nonFinalStages.add(new StageId(stage.getId().getQueryId(), stageNumber));
}
}
for (final StageId nonFinalStageId : nonFinalStages) {
if (!stageMap.containsKey(nonFinalStageId)) {
throw new ISE("Stage [%s] is missing a definition", nonFinalStageId);
}
}
final int finalStageCandidates = stageMap.size() - nonFinalStages.size();
if (finalStageCandidates == 1) {View on GitHub (pinned to 9b90983fd2)
Solutions
- Fix the builder/plan producer so each stage gets a unique stage number (use QueryDefinitionBuilder.add, which auto-numbers)
- Deduplicate the stage list before calling create and verify no two stages share getStageNumber()
- If merging plans, renumber stages of the second plan before merging
Example fix
// before: manual numbers collide builder.add(stage0Def); builder.add(stage0Def); // after QueryDefinitionBuilder builder = QueryDefinitionBuilder.builder(queryId); builder.add(StageDefinition.builder().nextStageNumber()...); // unique per stage
Defensive patterns
Strategy: validation
Validate before calling
Set<Integer> seen = new HashSet<>();
for (StageDefinition stage : stageDefinitions) {
if (!seen.add(stage.getStageNumber())) {
throw new IllegalArgumentException("Duplicate stage number: " + stage.getStageNumber());
}
}
QueryDefinition.create(stageDefinitions, context); Try / catch
try {
return QueryDefinition.create(stageDefinitions, context);
} catch (IllegalStateException e) {
throw new PlanValidationException("Duplicate stage numbers in plan", e);
} Prevention
- Use QueryDefinitionBuilder.add() which assigns sequential stage numbers
- Never build StageDefinitions with hardcoded stage numbers
- Deduplicate stages and renumber merged plans before create()
When it happens
Trigger: Calling QueryDefinition.create(stageDefinitions, context) with a list that contains two StageDefinitions with the same stage number for the same query id — typically a builder bug where a stage was added twice or stage numbering collided.
Common situations: Programmatic construction of query definitions (controller tests, custom engines reusing the MSQ kernel) where stage numbers are assigned manually instead of via QueryDefinitionBuilder.add(); merging plans from two sources without renumbering.
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
- Number of partitions not known for [%s] with maxPartitions[%
- Stage [%s] is missing a definition
- Must have a single final stage, but found [%d] candidates
- Work order for worker[%d] not found for stage[%d]
- Result partition information is not ready yet
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/ae92a5df58df6646.
Report an issue: GitHub.