{"record":{"id":"2889a65debd5452d","repo":"apache/beam","slug":"the-pipeline-contains-abandoned-ptransform-s","errorCode":null,"errorMessage":"The pipeline contains abandoned PTransform(s).","messagePattern":"The pipeline contains abandoned PTransform\\(s\\)\\.","errorType":"exception","errorClass":"AbandonedNodeException","httpStatus":null,"severity":"error","filePath":"sdks/java/core/src/main/java/org/apache/beam/sdk/testing/TestPipeline.java","lineNumber":239,"sourceCode":"\n      final List<TransformHierarchy.Node> runVisitedNodes =\n          checkStateNotNull(\n              this.runVisitedNodes,\n              \"Internal error: non-empty pipeline has been visited but still no runVisitedNodes\");\n      final List<TransformHierarchy.Node> pipelineNodes = recordPipelineNodes(pipeline);\n      if (runVisitedNodes.equals(pipelineNodes)) {\n        return;\n      }\n\n      final boolean hasDanglingPAssert =\n          pipelineNodes.stream()\n              .filter(Predicates.not(Predicates.in(runVisitedNodes)))\n              .anyMatch(isPAssertNode);\n\n      if (hasDanglingPAssert) {\n        throw new AbandonedNodeException(\"The pipeline contains abandoned PAssert(s).\");\n      } else {\n        throw new AbandonedNodeException(\"The pipeline contains abandoned PTransform(s).\");\n      }\n    }\n\n    private boolean pipelineRunSucceeded() {\n      return runVisitedNodes != null;\n    }\n\n    @Override\n    protected void afterPipelineExecution() {\n      runVisitedNodes = recordPipelineNodes(pipeline);\n      super.afterPipelineExecution();\n    }\n\n    @Override\n    protected void afterUserCodeFinished() {\n      super.afterUserCodeFinished();\n      verifyPipelineExecution();\n    }","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/core/src/main/java/org/apache/beam/sdk/testing/TestPipeline.java#L221-L257","documentation":"Same check as the abandoned PAssert case: if dangling pipeline nodes exist but they are not PAssert nodes, TestPipeline throws AbandonedNodeException stating the pipeline contains abandoned PTransform(s) — transforms present in the graph that the run never visited.","triggerScenarios":"PTransforms appended to the pipeline graph after run(), or transforms on branches the runner skipped/optimized away, detected by comparing pipelineNodes with runVisitedNodes.","commonSituations":"Mutating the pipeline after run() in test setup/teardown; constructing pipelines in multiple threads sharing one TestPipeline; applying transforms to a pipeline instance whose run() already completed.","solutions":["Build the complete pipeline graph before calling run(); do not mutate afterwards.","Ensure each TestPipeline is used by exactly one test and one run (create a fresh TestPipeline per test).","Check for runner-side pruning if transforms are legitimately unused — restructure so they are connected to a sink or assertion."],"exampleFix":"// before\npipeline.run();\nresult.apply(Count.globally());\n// after\nresult.apply(Count.globally());\npipeline.run().waitUntilFinish();","handlingStrategy":"validation","validationCode":"if (pipelineFinished) { throw new IllegalStateException(\"Do not apply transforms to a finished pipeline\"); }","typeGuard":null,"tryCatchPattern":"try { pipeline.run().waitUntilFinish(); } catch (AbandonedNodeException e) { fail(\"Pipeline graph was mutated after run(): \" + e.getMessage()); }","preventionTips":["Construct the full graph before run(); treat pipelines as immutable afterwards.","Use a fresh TestPipeline per test method.","Do not share TestPipeline across threads."],"tags":["java","testing","apache-beam","pipeline"],"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-14T16:17:12.679Z"}