{"record":{"id":"847636cb85cb7ebe","repo":"apache/beam","slug":"output-of-composite-transform-s-contains-a-s-produced-by-it","errorCode":null,"errorMessage":"Output of composite transform [%s] contains a %s produced by it. Only primitive transforms are permitted to produce %ss.%n    Outputs: %s%n    Other Producers: %s%n    Components: %s","messagePattern":"Output of composite transform \\[(.+?)\\] contains a (.+?) produced by it\\. Only primitive transforms are permitted to produce (.+?)s\\.%n    Outputs: (.+?)%n    Other Producers: (.+?)%n    Components: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/core/src/main/java/org/apache/beam/sdk/runners/TransformHierarchy.java","lineNumber":429,"sourceCode":"      checkState(\n          this.outputs == null, \"Tried to specify more than one output for %s\", getFullName());\n      checkNotNull(output, \"Tried to set the output of %s to null\", getFullName());\n      this.outputs = PValues.expandOutput(output);\n\n      // Validate that a primitive transform produces only primitive output, and a composite\n      // transform does not produce primitive output.\n      Set<Node> outputProducers = new HashSet<>();\n      for (PCollection<?> outputValue : PValues.expandOutput(output).values()) {\n        outputProducers.add(getProducer(outputValue));\n      }\n      if (outputProducers.contains(this) && (!parts.isEmpty() || outputProducers.size() > 1)) {\n        Set<String> otherProducerNames = new HashSet<>();\n        for (Node outputProducer : outputProducers) {\n          if (outputProducer != this) {\n            otherProducerNames.add(outputProducer.getFullName());\n          }\n        }\n        throw new IllegalArgumentException(\n            String.format(\n                \"Output of composite transform [%s] contains a %s produced by it. \"\n                    + \"Only primitive transforms are permitted to produce %ss.\"\n                    + \"%n    Outputs: %s\"\n                    + \"%n    Other Producers: %s\"\n                    + \"%n    Components: %s\",\n                getFullName(),\n                PCollection.class.getSimpleName(),\n                PCollection.class.getSimpleName(),\n                output.expand(),\n                otherProducerNames,\n                parts));\n      }\n    }\n\n    /**\n     * Replaces each value in {@code originalToReplacement} present in this {@link Node Node's}\n     * outputs with the key that maps to that value.","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/core/src/main/java/org/apache/beam/sdk/runners/TransformHierarchy.java#L411-L447","documentation":"Beam's TransformHierarchy validates that when a composite transform sets its output, the produced PCollection(s) are not also listed as outputs of the composite itself. Only primitive (leaf) transforms may produce PCollections. This invariant catches malformed pipeline graph construction where a composite both expands into children and claims their outputs.","triggerScenarios":"Triggered in setOutput during pipeline expansion when the output PCollections of a composite have a producer node other than the composite's own child — i.e. the output was produced by the composite while other producers were recorded for the same collections.","commonSituations":"Custom composite PTransform implementations that override expand() incorrectly and reuse/reassign output PCollections; runners or test harnesses building the transform tree manually and misattributing producers; using internal runner APIs to graft subgraphs.","solutions":["Fix the composite PTransform so apply()/expand() returns PCollections produced by primitive child transforms, not by the composite itself.","Do not call applyOutput/producer bookkeeping APIs directly on composite nodes; let expansion assign producers.","Check for custom RunnerApi pipeline expansion or overrides that remap PCollection producers.","Print the transform graph (e.g. with --experiments or graph dumps) and verify producer attribution of the collections listed in the message."],"exampleFix":"// before (composite directly returns input unchanged marked as own output)\nPCollection<T> expand(PCollection<T> in) { return in.apply(\"bad\", this); }\n\n// after: delegate to a primitive child\nPCollection<T> expand(PCollection<T> in) { return in.apply(\"good\", MapElements.via(...)); }","handlingStrategy":"validation","validationCode":"// In tests, expand the composite and assert outputs are produced by primitive children\nPCollectionList out = pipeline.apply(myComposite);\nassertNotEquals(myComposite, out.get(0).getProducer());","typeGuard":null,"tryCatchPattern":"try { pipeline.apply(\"t\", composite).apply(next); }\ncatch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"Only primitive transforms are permitted\")) {\n    throw new IllegalStateException(\"Composite transform misconfigured: \" + e.getMessage());\n  } throw e;\n}","preventionTips":["Only return child-produced PCollections from expand()","Don't touch internal TransformHierarchy producer APIs from composites","Test custom composites with a direct runner before production"],"tags":["pipeline-construction","beam-runner","transform-graph"],"backgroundTag":"invalid-state-transition","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"}