apache/beam · error · IllegalArgumentException

Expected outputs, but got exactly one.

Error message

Expected ${outputTags.size()} outputs, but got exactly one.

What it means

When outputTags are declared and the external transform returns a single PCollection, YamlTransform wraps it under one tag. If the declared outputTags count is not exactly 1, the single PCollection cannot satisfy the declared multi-output contract and IllegalArgumentException is thrown.

Solutions

  1. Declare exactly one output tag, or none, if the transform yields a single PCollection.
  2. Fix the external transform to actually emit all declared outputs (use withMultipleOutputs).
  3. Remove the extra declared output tags from the YAML spec.
  4. Check the transform docs for the true number of outputs.

Example fix

// before
.withOutputTags(ImmutableList.of(new TupleTag<>("a"), new TupleTag<>("b")), ...)
// after (transform emits one output)
.withOutputTags(ImmutableList.of(new TupleTag<>("a")), ...);
Defensive patterns

Strategy: validation

Validate before calling

if (outputTags.size() > 1 && transformEmitsSinglePCollection) {
  throw new IllegalStateException("Cannot declare " + outputTags.size() + " outputs for single-output transform");
}

Type guard

boolean outputCountMatches(int declared, Object out) { return declared == 1 || !(out instanceof PCollection); }

Try / catch

try {
  return expand(input);
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("outputs, but got exactly one")) { /* fix output tag count */ }
  throw e;
}

Prevention

When it happens

Trigger: Declaring 2+ output tags for a YamlTransform whose underlying external transform returns just one PCollection (single-output external transform paired with a multi-output spec).

Common situations: YAML spec over-declares outputs for a transform that was updated to emit only one; confusing single- and multi-output variants of the same Python transform.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/c89520da743f837e. Report an issue: GitHub.

Appendix: source

Thrown at sdks/java/extensions/yaml/src/main/java/org/apache/beam/sdk/extensions/yaml/YamlTransform.java:185

    } else if (input instanceof PCollectionRowTuple) {
      output = ((PCollectionRowTuple) input).apply(externalTransform);
    } else {
      throw new IllegalArgumentException("Unrecognized input type: " + input);
    }

    if (outputTags == null) {
      if (!(output instanceof PCollection)) {
        throw new IllegalArgumentException(
            "Expected a single PCollection output, but got "
                + output
                + ". Perhaps withMultipleOutputs() needs to be specified?");
      }
      return (OutputT) output;
    } else {
      if (output instanceof PCollection) {
        // ExternalPythonTransform always returns single outputs as PCollections.
        if (outputTags.size() != 1) {
          throw new IllegalArgumentException(
              "Expected " + outputTags.size() + " outputs, but got exactly one.");
        }
        return (OutputT)
            PCollectionRowTuple.of(outputTags.iterator().next(), (PCollection<Row>) output);
      } else {
        Map<TupleTag<?>, PValue> expandedOutputs = output.expand();
        Set<String> actualOutputTags =
            expandedOutputs.keySet().stream()
                .map(TupleTag::getId)
                .collect(Collectors.toCollection(HashSet::new));
        if (!outputTags.equals(actualOutputTags)) {
          throw new IllegalArgumentException(
              "Output has tags "
                  + Joiner.on(", ").join(actualOutputTags)
                  + " but expected output tags "
                  + Joiner.on(", ").join(outputTags));
        }
        PCollectionRowTuple result = PCollectionRowTuple.empty(input.getPipeline());

View on GitHub (pinned to 12126d8942)