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
- 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.
- If the code needs the key, move that logic into an @OnTimer method where the key is bound.
- 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
- In @ProcessElement, obtain the key via a @KeyParam parameter or by consuming KV<K, V> as the element.
- Only rely on invoker key() inside @OnTimer callbacks where the key is bound.
- Keep keyed vs non-keyed DoFn conventions documented in shared DoFn base classes.
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
- Cannot access FinishBundleContext outside of @FinishBundle…
- Cannot access sideInput in non-window observing context.
- Cannot access StartBundleContext outside of @StartBundle…
- Cannot access state in non-window observing context.
- Cannot access timer in non-window observing context.
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)