{"record":{"id":"9722018504a12e60","repo":"apache/beam","slug":"cannot-output-timer-with-output-timestamp-s-output","errorCode":null,"errorMessage":"Cannot output timer with output timestamp %s. Output timestamps must be no earlier than the timestamp of the current input (%s) minus the allowed skew (%s) and no later than %s. See the DoFn#getAllowedTimestampSkew() Javadoc for details on changing the allowed skew.","messagePattern":"Cannot output timer with output timestamp (.+?)\\. Output timestamps must be no earlier than the timestamp of the current input \\((.+?)\\) minus the allowed skew \\((.+?)\\) and no later than (.+?)\\. See the DoFn#getAllowedTimestampSkew\\(\\) Javadoc for details on changing the allowed skew\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java","lineNumber":1450,"sourceCode":"      return target;\n    }\n\n    private Timer<K> getClearedTimer() {\n      return Timer.cleared(userKey, dynamicTimerTag, Collections.singletonList(boundedWindow));\n    }\n\n    @SuppressWarnings(\"deprecation\") // Allowed Skew is deprecated for users, but must be respected\n    private Timer<K> getTimerForTime(Instant scheduledTime) {\n      if (outputTimestamp != null) {\n        Instant lowerBound;\n        try {\n          lowerBound = elementTimestampOrTimerHoldTimestamp.minus(doFn.getAllowedTimestampSkew());\n        } catch (ArithmeticException e) {\n          lowerBound = BoundedWindow.TIMESTAMP_MIN_VALUE;\n        }\n        if (outputTimestamp.isBefore(lowerBound)\n            || outputTimestamp.isAfter(BoundedWindow.TIMESTAMP_MAX_VALUE)) {\n          throw new IllegalArgumentException(\n              String.format(\n                  \"Cannot output timer with output timestamp %s. Output timestamps must be no \"\n                      + \"earlier than the timestamp of the current input (%s) minus the allowed skew \"\n                      + \"(%s) and no later than %s. See the DoFn#getAllowedTimestampSkew() Javadoc for \"\n                      + \"details on changing the allowed skew.\",\n                  outputTimestamp,\n                  elementTimestampOrTimerHoldTimestamp,\n                  doFn.getAllowedTimestampSkew().getMillis() >= Integer.MAX_VALUE\n                      ? doFn.getAllowedTimestampSkew()\n                      : PeriodFormat.getDefault().print(doFn.getAllowedTimestampSkew().toPeriod()),\n                  BoundedWindow.TIMESTAMP_MAX_VALUE));\n        }\n      }\n\n      // Output timestamp is set to the delivery time if not initialized by an user.\n      if (!noOutputTimestamp\n          && outputTimestamp == null\n          && TimeDomain.EVENT_TIME.equals(timeDomain)) {","sourceCodeStart":1432,"sourceCodeEnd":1468,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java#L1432-L1468","documentation":"Thrown when a DoFn sets a timer with an output timestamp that falls outside the permitted window: it must not be earlier than the current input (or timer hold) timestamp minus the DoFn's allowed timestamp skew, and not later than BoundedWindow.TIMESTAMP_MAX_VALUE. This enforces the DoFn#getAllowedTimestampSkew contract in the Fn API harness.","triggerScenarios":"Calling Timer.set(...)/offset/onTimer callbacks where the computed output timestamp is before inputTimestamp.minus(allowedTimestampSkew) or after TIMESTAMP_MAX_VALUE, inside a timer-aware DoFn executed by FnApiDoFnRunner.","commonSituations":"Setting a timer with a timestamp far in the past relative to the element's timestamp; using Instant.minus() that under/overflows; overriding getAllowedTimestampSkew with a small value while emitting early timestamps; windowing edge cases near the timestamp min/max bounds.","solutions":["Return a larger value from DoFn#getAllowedTimestampSkew() (e.g. Duration.standardHours(1)) so the lower bound covers your timer timestamps.","Clamp the timer's output timestamp to be >= input timestamp minus allowed skew before setting it.","Ensure the timer timestamp is not after BoundedWindow.TIMESTAMP_MAX_VALUE; clamp to that constant if needed.","Verify arithmetic on Instant timestamps (minus/add) isn't overflowing; the harness clamps overflow but the resulting comparison can still fail."],"exampleFix":"// before\n@GetAllowedTimestampSkew\npublic Duration getAllowedTimestampSkew() { return Duration.ZERO; }\ntimer.offset(Duration.standardMinutes(-30));\n\n// after\n@GetAllowedTimestampSkew\npublic Duration getAllowedTimestampSkew() { return Duration.standardHours(1); }","handlingStrategy":"validation","validationCode":"Duration skew = doFn.getAllowedTimestampSkew();\nInstant lower = inputTs.minus(skew);\nif (timerTs.isBefore(lower) || timerTs.isAfter(BoundedWindow.TIMESTAMP_MAX_VALUE)) {\n  timerTs = max(timerTs, lower); // clamp before setting\n}","typeGuard":null,"tryCatchPattern":"try {\n  timer.offset(d);\n} catch (IllegalArgumentException e) {\n  throw new IllegalStateException(\"Timer timestamp outside allowed skew; increase @GetAllowedTimestampSkew\", e);\n}","preventionTips":["Declare @GetAllowedTimestampSkew large enough for all backward timer timestamps","Clamp timer timestamps against input timestamp minus skew","Avoid Instant arithmetic that can overflow near TIMESTAMP_MIN/MAX"],"tags":["beam","timer","timestamp","allowed-timestamp-skew"],"backgroundTag":"value-out-of-range","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"}