apache/druid · error · IllegalStateException

Broadcast input number out of range

Error message

Broadcast input number out of range [%s]

What it means

During StageDefinition construction, each broadcast input number must be a valid index into the stage's inputSpecs map (0 <= n < inputSpecs.size()). Out-of-range values mean the stage graph references an input channel that does not exist, so ISE("Broadcast input number out of range [%s]") is thrown.

Solutions

  1. Renumber broadcast inputs so each is a 0-based index into the stage's inputSpecs
  2. Verify the stage actually has as many input specs as the highest broadcast number + 1
  3. If inputs were removed, rebuild the stage definition instead of mutating broadcast numbers

Example fix

// before
builder.broadcastInputNumbers(ImmutableIntList.of(2)); // stage has 2 inputs (0..1)
// after
builder.broadcastInputNumbers(ImmutableIntList.of(1));
Defensive patterns

Strategy: validation

Validate before calling

for (int n : broadcastInputNumbers) {
  if (n < 0 || n >= inputSpecs.size()) {
    throw new IllegalStateException("Broadcast input out of range: " + n);
  }
}

Try / catch

try { StageDefinition def = builder.build(); } catch (IllegalStateException e) { log.error("Stage graph error: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Building a StageDefinition with broadcastInputNumbers containing a negative value or an index >= number of input specs — e.g. declaring broadcastInput(3) on a stage with only 2 inputs, or reusing a broadcast number from a differently-shaped stage graph.

Common situations: Hand-assembling MSQ controller/worker stage graphs in tests or tooling, or planner bugs after removing stages/inputs without renumbering broadcast references.

Related errors


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

Appendix: source

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

    this.signature = Preconditions.checkNotNull(signature, "signature");
    this.shuffleSpec = shuffleSpec;
    this.maxWorkerCount = maxWorkerCount;
    this.shuffleCheckHasMultipleValues = shuffleCheckHasMultipleValues;
    this.frameReader = Suppliers.memoize(() -> FrameReader.create(signature))::get;

    if (mustGatherResultKeyStatistics() && shuffleSpec.clusterBy().isEmpty()) {
      throw new IAE("Cannot shuffle with spec [%s] and nil clusterBy", shuffleSpec);
    }

    for (final String columnName : signature.getColumnNames()) {
      if (!signature.getColumnType(columnName).isPresent()) {
        throw new ISE("Missing type for column [%s]", columnName);
      }
    }

    for (final int broadcastInputNumber : this.broadcastInputNumbers) {
      if (broadcastInputNumber < 0 || broadcastInputNumber >= inputSpecs.size()) {
        throw new ISE("Broadcast input number out of range [%s]", broadcastInputNumber);
      }
    }
  }

  public static boolean mustGatherResultKeyStatistics(@Nullable final ShuffleSpec shuffleSpec)
  {
    return shuffleSpec != null
           && shuffleSpec.kind() == ShuffleKind.GLOBAL_SORT
           && ((GlobalSortShuffleSpec) shuffleSpec).mustGatherResultKeyStatistics();
  }

  public static StageDefinitionBuilder builder(final int stageNumber)
  {
    return new StageDefinitionBuilder(stageNumber);
  }

  public static StageDefinitionBuilder builder(final StageDefinition stageDef)
  {

View on GitHub (pinned to 9b90983fd2)