apache/beam · error · UnsupportedOperationException

Cannot access key as parameter outside of @OnTimer method.

Error message

Cannot access key as parameter outside of @OnTimer method.

What it means

The invoker context used outside of @OnTimer methods throws UnsupportedOperationException when key() is requested as a parameter, because the key is only bound when firing a timer for a specific key. In @ProcessElement or other contexts the runner does not provide a key binding, so access is rejected.

Solutions

  1. Access the key in @ProcessElement by declaring a @KeyParam K key parameter or reading element() as a KV<K, V> — not via the timer-only key() accessor.
  2. If the code needs the key, move that logic into an @OnTimer method where the key is bound.
  3. In custom invoker code, only call key() for methods annotated with @OnTimer (or those declaring @KeyParam).

Example fix

// before
@ProcessElement
public void processElement(ProcessContext c) {
  Object k = key(); // throws outside @OnTimer
}

// after
@ProcessElement
public void processElement(ProcessContext c, @KeyParam K key) {
  // use key directly
}
Defensive patterns

Strategy: validation

Validate before calling

// key() via the invoker is only valid in @OnTimer; validate usage:
for (Method m : dofn.getClass().getDeclaredMethods()) {
  if (m.isAnnotationPresent(OnTimer.class) == false
      && Arrays.stream(m.getParameterTypes()).noneMatch(kvType -> kvType == KV.class)) {
    // ensure no code path calls invoker.key() for this method
  }
}

Type guard

boolean isOnTimerMethod(Method m) {
  return m.isAnnotationPresent(DoFn.OnTimer.class);
}

Try / catch

try {
  Object k = key();
} catch (UnsupportedOperationException e) {
  throw new IllegalStateException("key() is only bound in @OnTimer; use @KeyParam or KV element in @ProcessElement", e);
}

Prevention

When it happens

Trigger: Calling invoker-context.key() (or DoFn.KeyParam-injected equivalents) from @ProcessElement, @StartBundle, @FinishBundle, or non-timer callbacks; custom advice invoking key() for methods lacking a @KeyParam-parameter binding.

Common situations: Requesting the key via the invoker API in generic runner/framework code; unit tests invoking a DoFn's methods directly with the wrong context; expecting key() to work in a stateless @ProcessElement like it does in @OnTimer.

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/4ac1b3b6864b2f9e. Report an issue: GitHub.

Appendix: source

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

    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.");
    }

    @Override
    public Object schemaElement(int index) {
      SerializableFunction converter = doFnSchemaInformation.getElementConverters().get(index);
      return converter.apply(element());
    }

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

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

View on GitHub (pinned to 12126d8942)