{"record":{"id":"3ec7557d8ad5a38f","repo":"apache/beam","slug":"output-has-tags-actualoutputtags-but-expected-output-tags","errorCode":null,"errorMessage":"Output has tags ${actualOutputTags} but expected output tags ${outputTags}","messagePattern":"Output has tags (.+?) but expected output tags (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/extensions/yaml/src/main/java/org/apache/beam/sdk/extensions/yaml/YamlTransform.java","lineNumber":197,"sourceCode":"      }\n      return (OutputT) output;\n    } else {\n      if (output instanceof PCollection) {\n        // ExternalPythonTransform always returns single outputs as PCollections.\n        if (outputTags.size() != 1) {\n          throw new IllegalArgumentException(\n              \"Expected \" + outputTags.size() + \" outputs, but got exactly one.\");\n        }\n        return (OutputT)\n            PCollectionRowTuple.of(outputTags.iterator().next(), (PCollection<Row>) output);\n      } else {\n        Map<TupleTag<?>, PValue> expandedOutputs = output.expand();\n        Set<String> actualOutputTags =\n            expandedOutputs.keySet().stream()\n                .map(TupleTag::getId)\n                .collect(Collectors.toCollection(HashSet::new));\n        if (!outputTags.equals(actualOutputTags)) {\n          throw new IllegalArgumentException(\n              \"Output has tags \"\n                  + Joiner.on(\", \").join(actualOutputTags)\n                  + \" but expected output tags \"\n                  + Joiner.on(\", \").join(outputTags));\n        }\n        PCollectionRowTuple result = PCollectionRowTuple.empty(input.getPipeline());\n        for (Map.Entry<TupleTag<?>, PValue> subOutput : expandedOutputs.entrySet()) {\n          result = result.and(subOutput.getKey().getId(), (PCollection<Row>) subOutput.getValue());\n        }\n        return (OutputT) result;\n      }\n    }\n  }\n}\n","sourceCodeStart":179,"sourceCodeEnd":212,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/extensions/yaml/src/main/java/org/apache/beam/sdk/extensions/yaml/YamlTransform.java#L179-L212","documentation":"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.","triggerScenarios":"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}.","commonSituations":"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.","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."],"exampleFix":"// before\noutputs: [\"main\"]\n// after (transform also emits \"side\")\noutputs: [\"main\", \"side\"]","handlingStrategy":"validation","validationCode":"Set<String> declared = new HashSet<>(yamlSpecOutputs);\nSet<String> actual = result.expand().keySet().stream().map(TupleTag::getId).collect(Collectors.toSet());\nif (!declared.equals(actual)) throw new IllegalStateException(\"Output tag mismatch: \" + actual + \" vs \" + declared);","typeGuard":"boolean hasExpectedOutputTags(Object out, Set<String> expected) {\n  return !(out instanceof PCollection) && out.expand().keySet().stream().map(TupleTag::getId).collect(Collectors.toSet()).equals(expected);\n}","tryCatchPattern":"try {\n  return expand(input);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"Output has tags\")) { /* sync spec with transform */ }\n  throw e;\n}","preventionTips":["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"],"tags":["apache-beam","java","yaml-transform","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}