{"record":{"id":"87e705e8b2227a0b","repo":"apache/beam","slug":"cannot-access-fire-timestamp-outside-of-ontimer-method","errorCode":null,"errorMessage":"Cannot access fire timestamp outside of @OnTimer method.","messagePattern":"Cannot access fire timestamp 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":2066,"sourceCode":"\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    }\n\n    @Override\n    public @Nullable Long currentRecordOffset(DoFn<InputT, OutputT> doFn) {\n      return currentRecordOffset();\n    }\n\n    @Override\n    public Instant fireTimestamp(DoFn<InputT, OutputT> doFn) {\n      throw new UnsupportedOperationException(\n          \"Cannot access fire timestamp outside of @OnTimer method.\");\n    }\n\n    @Override\n    public String timerId(DoFn<InputT, OutputT> doFn) {\n      throw new UnsupportedOperationException(\n          \"Cannot access timerId as parameter outside of @OnTimer method.\");\n    }\n\n    @Override\n    public TimeDomain timeDomain(DoFn<InputT, OutputT> doFn) {\n      throw new UnsupportedOperationException(\n          \"Cannot access time domain outside of @ProcessTimer method.\");\n    }\n\n    @Override\n    public OutputReceiver<OutputT> outputReceiver(DoFn<InputT, OutputT> doFn) {\n      return this;","sourceCodeStart":2048,"sourceCodeEnd":2084,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java#L2048-L2084","documentation":"In Apache Beam's Fn API Java harness, DoFnSupportT.fireTimestamp() is the accessor backing the TimeParam/Instant 'fire timestamp' available only while executing a @OnTimer (or timer-firing) callback. FnApiDoFnRunner's ProcessContext implementation does not track a fire timestamp for ordinary element processing, so any request for the fire timestamp outside a timer callback throws UnsupportedOperationException. This guards against reading timer state that is meaningless during normal bundle/element processing.","triggerScenarios":"Calling doFnProcessContext.fireTimestamp() (or injecting an Instant via @Timestamp-style timer param resolution, e.g. DoFnSignatures requesting TimeDomain/Instant params) from a @ProcessElement, @StartBundle, or @FinishBundle method instead of from inside a @OnTimer method. Also occurs when DoFn signature inspection wires fireTimestamp into a non-timer invocation path.","commonSituations":"A developer refactors timer-handling code and reuses a helper that reads fireTimestamp from the context in the element path; a DoFn declares a fire-timestamp parameter but the method is not annotated @OnTimer; copying timer logic into @ProcessElement while migrating from the classic runner to the portable Fn API harness.","solutions":["Move the fireTimestamp() call (or the Instant/timer-timestamp parameter) into a method annotated with @OnTimer only.","If the timestamp is needed for elements, use the element's timestamp instead: c.timestamp() on the ProcessContext, which is valid inside @ProcessElement.","If firing info must flow to non-timer code, capture fireTimestamp inside the @OnTimer method and pass it explicitly (e.g. as a method argument or state variable).","Verify the method's annotations with DoFnSigatures/annotation checks so the harness binds timer parameters only for @OnTimer methods."],"exampleFix":"// before\n@ProcessElement\npublic void processElement(ProcessContext c) {\n  Instant ts = c.fireTimestamp(); // throws\n  ...\n}\n// after\n@ProcessElement\npublic void processElement(ProcessContext c) {\n  Instant ts = c.timestamp(); // element timestamp, always valid\n  ...\n}\n@OnTimer(\"myTimer\")\npublic void onTimer(OnTimerContext c) {\n  Instant fireTs = c.fireTimestamp(); // valid here\n  ...\n}","handlingStrategy":"validation","validationCode":"// Before reading a timer-only value, confirm you are in a timer callback:\nboolean inOnTimer = getClass().getEnclosingMethod() != null\n    && getClass().getEnclosingMethod().isAnnotationPresent(OnTimer.class);\nif (!inOnTimer) {\n  Instant ts = processContext.timestamp(); // element timestamp instead of fireTimestamp()\n}","typeGuard":"static boolean canReadFireTimestamp(DoFn.ProcessContext c) {\n  return c instanceof DoFn.OnTimerContext;\n}","tryCatchPattern":"try {\n  Instant fireTs = ((DoFn.OnTimerContext) c).fireTimestamp();\n} catch (UnsupportedOperationException e) {\n  // not in a @OnTimer callback; fall back to element timestamp\n  Instant ts = ((DoFn.ProcessContext) c).timestamp();\n}","preventionTips":["Only read fireTimestamp/timerId/timeDomain inside @OnTimer methods","Use c.timestamp() for element timestamps in @ProcessElement","Keep timer-path and element-path code in separate methods so timer-only params never leak","Add a unit test that invokes each DoFn lifecycle method through a real runner"],"tags":["java","apache-beam","timers","unsupported-operation","dofn"],"backgroundTag":"unsupported-operation","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}