{"record":{"id":"5ecf0e2797c1d1b5","repo":"apache/beam","slug":"cannot-access-finishbundlecontext-outside-of-finishbundle","errorCode":null,"errorMessage":"Cannot access FinishBundleContext outside of @FinishBundle method.","messagePattern":"Cannot access FinishBundleContext outside of @FinishBundle method\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java","lineNumber":2023,"sourceCode":"    private ProcessBundleContextBase() {\n      doFn.super();\n    }\n\n    @Override\n    public PaneInfo paneInfo(DoFn<InputT, OutputT> doFn) {\n      return pane();\n    }\n\n    @Override\n    public DoFn<InputT, OutputT>.StartBundleContext startBundleContext(DoFn<InputT, OutputT> doFn) {\n      throw new UnsupportedOperationException(\n          \"Cannot access StartBundleContext outside of @StartBundle method.\");\n    }\n\n    @Override\n    public DoFn<InputT, OutputT>.FinishBundleContext finishBundleContext(\n        DoFn<InputT, OutputT> doFn) {\n      throw new UnsupportedOperationException(\n          \"Cannot access FinishBundleContext outside of @FinishBundle method.\");\n    }\n\n    @Override\n    public DoFn<InputT, OutputT>.ProcessContext processContext(DoFn<InputT, OutputT> doFn) {\n      return this;\n    }\n\n    @Override\n    public InputT element(DoFn<InputT, OutputT> doFn) {\n      return element();\n    }\n\n    @Override\n    public Object key() {\n      throw new UnsupportedOperationException(\n          \"Cannot access key as parameter outside of @OnTimer method.\");\n    }","sourceCodeStart":2005,"sourceCodeEnd":2041,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java#L2005-L2041","documentation":"The invoker context used outside of @FinishBundle methods throws UnsupportedOperationException when finishBundleContext(doFn) is requested, because FinishBundleContext — which allows emitting final outputs at bundle completion — only exists while running a @FinishBundle method. Other lifecycle contexts deliberately reject this accessor.","triggerScenarios":"Calling invoker-context.finishBundleContext(doFn) from @ProcessElement, @StartBundle, @OnTimer, or state/timer methods; custom DoFnInvoker advice invoking the accessor unconditionally.","commonSituations":"Trying to flush accumulated results to the output from within @ProcessElement via FinishBundleContext; generic framework or testing code that calls every context accessor; misunderstanding which context object each annotated method receives.","solutions":["Only call finishBundleContext/doFn.finishBundle-style output inside a @FinishBundle method; move flushing logic there.","Emit per-element results with the ProcessContext from @ProcessElement instead of deferring through the finish context.","In custom invoker advice, guard the accessor with the lifecycle phase check before invoking."],"exampleFix":"// before\n@ProcessElement\npublic void processElement(ProcessContext c) {\n  finishBundleContext(...).output(tag, accumulated); // throws\n}\n\n// after\n@FinishBundle\npublic void finishBundle(FinishBundleContext ctx) {\n  ctx.output(tag, accumulated, Instant.EPOCH, GlobalWindow.INSTANCE);\n}","handlingStrategy":"validation","validationCode":"// Only use FinishBundleContext inside @FinishBundle methods:\nfor (Method m : dofn.getClass().getDeclaredMethods()) {\n  if (m.getParameterCount() > 0\n      && DoFn.FinishBundleContext.class.isAssignableFrom(m.getParameterTypes()[0])\n      && !m.isAnnotationPresent(FinishBundle.class)) {\n    throw new IllegalStateException(m + \" takes FinishBundleContext but is not @FinishBundle\");\n  }\n}","typeGuard":"boolean isFinishBundleMethod(Method m) {\n  return m.isAnnotationPresent(DoFn.FinishBundle.class);\n}","tryCatchPattern":"try {\n  ctx.finishBundleContext(doFn).output(tag, v, ts, window);\n} catch (UnsupportedOperationException e) {\n  throw new IllegalStateException(\"finishBundleContext is only valid inside @FinishBundle\", e);\n}","preventionTips":["Reserve FinishBundleContext for @FinishBundle methods that flush accumulated results.","Accumulate in instance fields during @ProcessElement, emit in @FinishBundle.","Guard custom invoker advice against unconditional lifecycle context access."],"tags":["java","beam","apache-beam","dofn","lifecycle"],"backgroundTag":"unsupported-operation","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"}