apache/beam · error · IllegalArgumentException
Output has tags but expected output tags
Error message
Output has tags ${actualOutputTags} but expected output tags ${outputTags} What it means
After expand(), YamlTransform compares the ids of the output TupleTags actually produced by the external transform against the output tags declared in the spec. A set mismatch (missing, extra, or renamed outputs) throws IllegalArgumentException listing both sets joined by commas.
Solutions
- Update the declared outputTags in the YAML/Java spec to exactly match the tags the transform emits.
- Change the external transform to emit exactly the declared set of output tags.
- Diff the two tag sets printed in the message to find extra/missing/renamed tags.
- Pin the external transform version so its output contract matches the spec.
Example fix
// before outputs: ["main"] // after (transform also emits "side") outputs: ["main", "side"]
Defensive patterns
Strategy: validation
Validate before calling
Set<String> declared = new HashSet<>(yamlSpecOutputs);
Set<String> actual = result.expand().keySet().stream().map(TupleTag::getId).collect(Collectors.toSet());
if (!declared.equals(actual)) throw new IllegalStateException("Output tag mismatch: " + actual + " vs " + declared); Type guard
boolean hasExpectedOutputTags(Object out, Set<String> expected) {
return !(out instanceof PCollection) && out.expand().keySet().stream().map(TupleTag::getId).collect(Collectors.toSet()).equals(expected);
} Try / catch
try {
return expand(input);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Output has tags")) { /* sync spec with transform */ }
throw e;
} Prevention
- Pin external transform versions to keep output contracts stable
- Diff emitted vs declared output tags when upgrading transforms
- Name output tags explicitly, never rely on auto-generated ids
When it happens
Trigger: Running a YamlTransform whose underlying external transform's expanded output map keys differ from the declared outputTags — e.g. external transform emits tags {main, side} while spec declares only {main}.
Common situations: Upgrading a Python external transform that added a new output while the YAML spec stayed unchanged; typos in declared output tag names; auto-generated TupleTag ids not matching spec.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Input has tags but expected input tags
- Batch size is too large! It should be smaller or equal than
- Both clientCertPath and clientCertKeyPath must be specified…
- ChangeStreamName can't be empty
- Column delimiter should be set if headers are present.
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/3ec7557d8ad5a38f.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/java/extensions/yaml/src/main/java/org/apache/beam/sdk/extensions/yaml/YamlTransform.java:197
}
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());
for (Map.Entry<TupleTag<?>, PValue> subOutput : expandedOutputs.entrySet()) {
result = result.and(subOutput.getKey().getId(), (PCollection<Row>) subOutput.getValue());
}
return (OutputT) result;
}
}
}
}
View on GitHub (pinned to 12126d8942)