{"record":{"id":"ae92a5df58df6646","repo":"apache/druid","slug":"cannot-accept-duplicate-stage-numbers","errorCode":null,"errorMessage":"Cannot accept duplicate stage numbers","messagePattern":"Cannot accept duplicate stage numbers","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/kernel/QueryDefinition.java","lineNumber":84,"sourceCode":"  )\n  {\n    this.stageDefinitions = stageDefinitions;\n    this.finalStage = finalStage;\n    this.context = context;\n  }\n\n  @JsonCreator\n  public static QueryDefinition create(\n      @JsonProperty(\"stages\") final List<StageDefinition> stageDefinitions,\n      @Nullable @JsonProperty(\"context\") final QueryContext context)\n  {\n    final Map<StageId, StageDefinition> stageMap = new HashMap<>();\n    final Set<StageId> nonFinalStages = new HashSet<>();\n    final IntSet stageNumbers = new IntOpenHashSet();\n\n    for (final StageDefinition stage : stageDefinitions) {\n      if (!stageNumbers.add(stage.getStageNumber())) {\n        throw new ISE(\"Cannot accept duplicate stage numbers\");\n      }\n\n      stageMap.put(stage.getId(), stage);\n\n      for (int stageNumber : stage.getInputStageNumbers()) {\n        nonFinalStages.add(new StageId(stage.getId().getQueryId(), stageNumber));\n      }\n    }\n\n    for (final StageId nonFinalStageId : nonFinalStages) {\n      if (!stageMap.containsKey(nonFinalStageId)) {\n        throw new ISE(\"Stage [%s] is missing a definition\", nonFinalStageId);\n      }\n    }\n\n    final int finalStageCandidates = stageMap.size() - nonFinalStages.size();\n\n    if (finalStageCandidates == 1) {","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/kernel/QueryDefinition.java#L66-L102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: manual numbers collide\nbuilder.add(stage0Def); builder.add(stage0Def);\n// after\nQueryDefinitionBuilder builder = QueryDefinitionBuilder.builder(queryId);\nbuilder.add(StageDefinition.builder().nextStageNumber()...); // unique per stage","handlingStrategy":"validation","validationCode":"Set<Integer> seen = new HashSet<>();\nfor (StageDefinition stage : stageDefinitions) {\n  if (!seen.add(stage.getStageNumber())) {\n    throw new IllegalArgumentException(\"Duplicate stage number: \" + stage.getStageNumber());\n  }\n}\nQueryDefinition.create(stageDefinitions, context);","typeGuard":null,"tryCatchPattern":"try {\n  return QueryDefinition.create(stageDefinitions, context);\n} catch (IllegalStateException e) {\n  throw new PlanValidationException(\"Duplicate stage numbers in plan\", e);\n}","preventionTips":["Use QueryDefinitionBuilder.add() which assigns sequential stage numbers","Never build StageDefinitions with hardcoded stage numbers","Deduplicate stages and renumber merged plans before create()"],"tags":["msq","query-definition","duplicate-stage","internal-state"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}