{"record":{"id":"1d8813f89869cd87","repo":"apache/beam","slug":"cannot-access-ontimercontext-outside-of-ontimer-methods","errorCode":null,"errorMessage":"Cannot access OnTimerContext outside of @OnTimer methods.","messagePattern":"Cannot access OnTimerContext outside of @OnTimer methods\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java","lineNumber":2211,"sourceCode":"\n    @Override\n    public MultiOutputReceiver taggedOutputReceiver(DoFn<InputT, OutputT> doFn) {\n      return taggedOutputReceiver;\n    }\n\n    @Override\n    public BundleFinalizer bundleFinalizer() {\n      return bundleFinalizer;\n    }\n\n    @Override\n    public Object restriction() {\n      return currentRestriction;\n    }\n\n    @Override\n    public DoFn<InputT, OutputT>.OnTimerContext onTimerContext(DoFn<InputT, OutputT> doFn) {\n      throw new UnsupportedOperationException(\n          \"Cannot access OnTimerContext outside of @OnTimer methods.\");\n    }\n\n    @Override\n    public DoFn<InputT, OutputT>.OnWindowExpirationContext onWindowExpirationContext(\n        DoFn<InputT, OutputT> doFn) {\n      throw new UnsupportedOperationException(\n          \"Cannot access OnWindowExpirationContext outside of @OnWindowExpiration methods.\");\n    }\n\n    @Override\n    public RestrictionTracker<?, ?> restrictionTracker() {\n      return currentTracker;\n    }\n\n    @Override\n    public PipelineOptions getPipelineOptions() {\n      return pipelineOptions;","sourceCodeStart":2193,"sourceCodeEnd":2229,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java#L2193-L2229","documentation":"This accessor supplies a DoFn<InputT, OutputT>.OnTimerContext to user code. OnTimerContext only exists while the Fn API harness is dispatching a fired timer; the runner's context object is shared across all DoFn stages of a bundle, so requesting it at any other point throws UnsupportedOperationException. It prevents user code from treating an ordinary ProcessContext as a timer context.","triggerScenarios":"Calling context.onTimerContext(doFn) (directly or via DoFn runner plumbing, e.g. StartBundleContext/FinishBundleContext/BelowWatermark paths that ask for an OnTimerContext) outside the execution of a @OnTimer method — during @ProcessElement, bundle start/finish, or window expiration.","commonSituations":"Custom runner/harness code or test harnesses invoking DoFnRunner methods with the wrong context kind; user code casting the ProcessContext and calling onTimerContext; test utilities that invoke DoFn methods reflectively without setting up a timer dispatch context.","solutions":["Only use OnTimerContext inside @OnTimer-annotated methods; for element processing use ProcessContext.","In test code, invoke timer methods through a runner that simulates timer firing (e.g. TestStream with timer events) instead of calling onTimerContext manually.","If writing harness-level code, construct the appropriate context subclass for each lifecycle phase rather than reusing onTimerContext.","Check for accidental casts/miswired context lookups if this surfaces from framework code rather than your DoFn."],"exampleFix":"// before (test invoking DoFn directly)\nc.onTimerContext(myDoFn).timerId(); // throws\n// after\n// run through a runner that dispatches @OnTimer:\n// PCollection<?> out = p.apply(\"set timer\", Create.of(...))\n//     .apply(TestStream/trigger timers) so onTimer runs with a real OnTimerContext","handlingStrategy":"type-guard","validationCode":"if (!(c instanceof DoFn.OnTimerContext)) {\n  throw new IllegalStateException(\"onTimerContext requested outside @OnTimer\");\n}","typeGuard":"static <I, O> boolean isOnTimerContext(DoFn<I, O>.ProcessContext c) {\n  return c instanceof DoFn.OnTimerContext;\n}","tryCatchPattern":"try {\n  DoFn.OnTimerContext tc = runner.onTimerContext(doFn);\n  // use tc\n} catch (UnsupportedOperationException e) {\n  // wrong lifecycle phase: fall back to ProcessContext behavior\n}","preventionTips":["Use OnTimerContext only in @OnTimer methods","In tests, drive timers via TestStream or a runner that simulates timer firing","Do not cast ProcessContext to OnTimerContext speculatively","When writing harness-level code, construct the context type matching the lifecycle phase"],"tags":["java","apache-beam","timers","context","unsupported-operation"],"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"}