apache/druid · error · IllegalStateException

No such stage [%s]

Error message

No such stage [%s]

What it means

QueryDefinitionBuilder.getStageBuilder(stageNumber) looks up a previously added stage builder by number and throws ISE when no builder with that number exists. It is used by helpers like leftBuilder/rightBuilder to wire inputs into existing stages, so a wrong or out-of-range stage number breaks plan assembly.

Source

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

  }

  /**
   * Returns a number that is higher than all current stage numbers.
   */
  public int getNextStageNumber()
  {
    return stageBuilders.stream().mapToInt(StageDefinitionBuilder::getStageNumber).max().orElse(-1) + 1;
  }

  public StageDefinitionBuilder getStageBuilder(final int stageNumber)
  {
    for (final StageDefinitionBuilder stageBuilder : stageBuilders) {
      if (stageBuilder.getStageNumber() == stageNumber) {
        return stageBuilder;
      }
    }

    throw new ISE("No such stage [%s]", stageNumber);
  }

  public QueryDefinition build()
  {
    final List<StageDefinition> stageDefinitions =
        stageBuilders.stream().map(builder -> builder.build(queryId)).collect(Collectors.toList());
    return QueryDefinition.create(stageDefinitions, null);
  }
}

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Add the input stage builders first, then call leftBuilder/rightBuilder with their actual stage numbers
  2. Use StageDefinitionBuilder's assigned stage number (getStageNumber()) instead of hardcoding indices
  3. Check the builder's stage list size before referencing a stage number

Example fix

// before: hardcoded stage number
builder.add(StageDefinition.builder().id(new StageId(queryId, 2)).leftStageNumber(0)...);
StageDefinitionBuilder left = builder.leftBuilder(0); // ok, but fragile with wrong guesses
// after
int leftNum = builder.add(leftInputDef).getStageNumber();
int rightNum = builder.add(rightInputDef).getStageNumber();
StageDefinitionBuilder left = builder.getStageBuilder(leftNum);
Defensive patterns

Strategy: validation

Validate before calling

if (builder.hasStage(stageNumber)) { // or track added numbers yourself
  builder.getStageBuilder(stageNumber);
} else {
  throw new IllegalArgumentException("Stage " + stageNumber + " not added to builder yet");
}

Type guard

static boolean stageExists(List<StageDefinitionBuilder> builders, int stageNumber) {
  return builders.stream().anyMatch(b -> b.getStageNumber() == stageNumber);
}

Try / catch

try {
  return builder.getStageBuilder(stageNumber);
} catch (IllegalStateException e) {
  throw new PlanBuildException("Referenced stage not yet added: " + stageNumber, e);
}

Prevention

When it happens

Trigger: Calling leftBuilder/rightBuilder (or getStageBuilder directly) with a stage number that was never added to this builder — e.g. building a join stage that references input stage numbers not yet added or numbered differently than assumed.

Common situations: Manual stage numbering mistakes in multi-stage plan construction; assuming stage numbers start at 1 when they start at 0 (or vice versa); referencing stages of another query's builder.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/c1e7a4a7d0b34b9c. Report an issue: GitHub.