apache/beam · error · UnsupportedOperationException

Cannot access FinishBundleContext outside of @FinishBundle m

Error message

Cannot access FinishBundleContext outside of @FinishBundle method.

What it means

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.

Source

Thrown at sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java:2023

    private ProcessBundleContextBase() {
      doFn.super();
    }

    @Override
    public PaneInfo paneInfo(DoFn<InputT, OutputT> doFn) {
      return pane();
    }

    @Override
    public DoFn<InputT, OutputT>.StartBundleContext startBundleContext(DoFn<InputT, OutputT> doFn) {
      throw new UnsupportedOperationException(
          "Cannot access StartBundleContext outside of @StartBundle method.");
    }

    @Override
    public DoFn<InputT, OutputT>.FinishBundleContext finishBundleContext(
        DoFn<InputT, OutputT> doFn) {
      throw new UnsupportedOperationException(
          "Cannot access FinishBundleContext outside of @FinishBundle method.");
    }

    @Override
    public DoFn<InputT, OutputT>.ProcessContext processContext(DoFn<InputT, OutputT> doFn) {
      return this;
    }

    @Override
    public InputT element(DoFn<InputT, OutputT> doFn) {
      return element();
    }

    @Override
    public Object key() {
      throw new UnsupportedOperationException(
          "Cannot access key as parameter outside of @OnTimer method.");
    }

View on GitHub (pinned to 12126d8942)

Solutions

  1. Only call finishBundleContext/doFn.finishBundle-style output inside a @FinishBundle method; move flushing logic there.
  2. Emit per-element results with the ProcessContext from @ProcessElement instead of deferring through the finish context.
  3. In custom invoker advice, guard the accessor with the lifecycle phase check before invoking.

Example fix

// before
@ProcessElement
public void processElement(ProcessContext c) {
  finishBundleContext(...).output(tag, accumulated); // throws
}

// after
@FinishBundle
public void finishBundle(FinishBundleContext ctx) {
  ctx.output(tag, accumulated, Instant.EPOCH, GlobalWindow.INSTANCE);
}
Defensive patterns

Strategy: validation

Validate before calling

// Only use FinishBundleContext inside @FinishBundle methods:
for (Method m : dofn.getClass().getDeclaredMethods()) {
  if (m.getParameterCount() > 0
      && DoFn.FinishBundleContext.class.isAssignableFrom(m.getParameterTypes()[0])
      && !m.isAnnotationPresent(FinishBundle.class)) {
    throw new IllegalStateException(m + " takes FinishBundleContext but is not @FinishBundle");
  }
}

Type guard

boolean isFinishBundleMethod(Method m) {
  return m.isAnnotationPresent(DoFn.FinishBundle.class);
}

Try / catch

try {
  ctx.finishBundleContext(doFn).output(tag, v, ts, window);
} catch (UnsupportedOperationException e) {
  throw new IllegalStateException("finishBundleContext is only valid inside @FinishBundle", e);
}

Prevention

When it happens

Trigger: Calling invoker-context.finishBundleContext(doFn) from @ProcessElement, @StartBundle, @OnTimer, or state/timer methods; custom DoFnInvoker advice invoking the accessor unconditionally.

Common situations: 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.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/5ecf0e2797c1d1b5. Report an issue: GitHub.