{"record":{"id":"4ac1b3b6864b2f9e","repo":"apache/beam","slug":"cannot-access-key-as-parameter-outside-of-ontimer-method-4ac1b3","errorCode":null,"errorMessage":"Cannot access key as parameter outside of @OnTimer method.","messagePattern":"Cannot access key as parameter outside of @OnTimer method\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java","lineNumber":2039,"sourceCode":"    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    }\n\n    @Override\n    public Object schemaElement(int index) {\n      SerializableFunction converter = doFnSchemaInformation.getElementConverters().get(index);\n      return converter.apply(element());\n    }\n\n    @Override\n    public Instant timestamp(DoFn<InputT, OutputT> doFn) {\n      return timestamp();\n    }\n\n    @Override\n    public @Nullable String currentRecordId(DoFn<InputT, OutputT> doFn) {\n      return currentRecordId();\n    }","sourceCodeStart":2021,"sourceCodeEnd":2057,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java#L2021-L2057","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\n@ProcessElement\npublic void processElement(ProcessContext c) {\n  Object k = key(); // throws outside @OnTimer\n}\n\n// after\n@ProcessElement\npublic void processElement(ProcessContext c, @KeyParam K key) {\n  // use key directly\n}","handlingStrategy":"validation","validationCode":"// key() via the invoker is only valid in @OnTimer; validate usage:\nfor (Method m : dofn.getClass().getDeclaredMethods()) {\n  if (m.isAnnotationPresent(OnTimer.class) == false\n      && Arrays.stream(m.getParameterTypes()).noneMatch(kvType -> kvType == KV.class)) {\n    // ensure no code path calls invoker.key() for this method\n  }\n}","typeGuard":"boolean isOnTimerMethod(Method m) {\n  return m.isAnnotationPresent(DoFn.OnTimer.class);\n}","tryCatchPattern":"try {\n  Object k = key();\n} catch (UnsupportedOperationException e) {\n  throw new IllegalStateException(\"key() is only bound in @OnTimer; use @KeyParam or KV element in @ProcessElement\", e);\n}","preventionTips":["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."],"tags":["java","beam","apache-beam","dofn","keyparam"],"backgroundTag":"unsupported-operation","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}