apache/beam · error · Exception

Unexpected time domain

Error message

Unexpected time domain: %s

What it means

The streaming trigger driver's timer callback switches on time_domain and raises a bare Exception('Unexpected time domain: %s') for any domain other than WATERMARK (the only one its on-timer logic handles). REAL_TIME / DEPENDENT_REAL_TIME timers reaching this driver hit this fallback.

Solutions

  1. Only set TimeDomain.WATERMARK timers from trigger code, or handle real-time timers elsewhere (e.g. via the runner's timer API directly).
  2. If you need wall-clock behavior, encode it in the watermark hold or use a runner that supports real-time timers outside TriggerDriver.
  3. Check how the timer's time_domain was persisted; fix the code path that created a non-WATERMARK timer for this driver.

Example fix

// before
state.set_timer(window, 'fire', TimeDomain.REAL_TIME, ts)  # driver raises on fire
// after
state.set_timer(window, 'fire', TimeDomain.WATERMARK, ts)
Defensive patterns

Strategy: validation

Validate before calling

from apache_beam.transforms.timeutil import TimeDomain
assert time_domain == TimeDomain.WATERMARK, 'TriggerDriver only supports WATERMARK timers'

Type guard

def is_watermark_timer(td):
    from apache_beam.transforms.timeutil import TimeDomain
    return td == TimeDomain.WATERMARK

Try / catch

try:
    driver.on_timer(...)
except Exception as e:
    logger.error('Timer delivery failed: %s', e)
    raise

Prevention

When it happens

Trigger: A timer set with TimeDomain.REAL_TIME or TimeDomain.DEPENDENT_REAL_TIME fires and is delivered to this trigger driver's callback, which only implements the WATERMARK branch; or a corrupted/foreign time_domain value arrives from state serialization.

Common situations: Custom triggers or user code setting real-time timers that the built-in TriggerDriver does not support; Beam version drift where new time domains exist but this driver was not updated; tests firing timers with all domains.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/0c2f9c00eeaa72ef. Report an issue: GitHub.

Appendix: source

Thrown at sdks/python/apache_beam/transforms/trigger.py:1523

    if self.is_merging:
      state = MergeableStateAdapter(state)
    window = state.get_window(window_id)
    if state.get_state(window, self.TOMBSTONE):
      return

    if time_domain in (TimeDomain.WATERMARK, TimeDomain.REAL_TIME):
      if not self.is_merging or window in state.known_windows():
        context = state.at(window, self.clock)
        if self.trigger_fn.should_fire(time_domain, timestamp, window, context):
          finished = self.trigger_fn.on_fire(timestamp, window, context)
          yield self._output(
              window,
              finished,
              state,
              timestamp,
              time_domain == TimeDomain.WATERMARK)
    else:
      raise Exception('Unexpected time domain: %s' % time_domain)

  def _output(self, window, finished, state, output_watermark, maybe_ontime):
    """Output window and clean up if appropriate."""
    index = state.get_state(window, self.INDEX)
    state.add_state(window, self.INDEX, 1)
    if output_watermark <= window.max_timestamp():
      nonspeculative_index = -1
      timing = windowed_value.PaneInfoTiming.EARLY
      if state.get_state(window, self.NONSPECULATIVE_INDEX):
        nonspeculative_index = state.get_state(
            window, self.NONSPECULATIVE_INDEX)
        state.add_state(window, self.NONSPECULATIVE_INDEX, 1)
        _LOGGER.warning(
            'Watermark moved backwards in time '
            'or late data moved window end forward.')
    else:
      nonspeculative_index = state.get_state(window, self.NONSPECULATIVE_INDEX)
      state.add_state(window, self.NONSPECULATIVE_INDEX, 1)

View on GitHub (pinned to 12126d8942)