apache/beam · error · ValueError

Multiple on_timer callbacks registered for %r.

Error message

Multiple on_timer callbacks registered for %r.

What it means

Each TimerSpec can have exactly one expiry callback: _inner checks timer_spec._attached_callback and raises ValueError if it is already set. Registering @on_timer(SAME_SPEC) on two methods would leave the runner unable to know which callback to fire, so the second registration fails.

Solutions

  1. Create a separate TimerSpec for each callback: TIMER_SPEC_A, TIMER_SPEC_B, each with its own @on_timer decorator
  2. Remove the duplicate @on_timer registration so each spec has exactly one callback
  3. If you need both event-time and processing-time behavior in one DoFn, define two specs with different TimeDomains

Example fix

// before
@on_timer(TIMER_SPEC)
def cb_a(self): ...
@on_timer(TIMER_SPEC)
def cb_b(self): ...
// after
TIMER_SPEC_A = TimerSpec('timer_a', TimeDomain.WATERMARK)
TIMER_SPEC_B = TimerSpec('timer_b', TimeDomain.REAL_TIME)
@on_timer(TIMER_SPEC_A)
def cb_a(self): ...
@on_timer(TIMER_SPEC_B)
def cb_b(self): ...
Defensive patterns

Strategy: validation

Validate before calling

specs_seen = set()
for m in (cb_a, cb_b):
    assert getattr(m, '_timer_spec', None) not in specs_seen
    specs_seen.add(getattr(m, '_timer_spec', None))

Try / catch

try:
    _ = MyDoFn()
except ValueError as e:
    if 'Multiple on_timer callbacks' in str(e):
        log.error('two methods share the same TimerSpec; split the spec')

Prevention

When it happens

Trigger: Decorating two methods in the same DoFn with @on_timer(TIMER_SPEC) using the same spec; accidentally duplicating a decorated method (or importing/defining the callback twice); re-running the decorator on the same spec object at module reload in an unusual setup.

Common situations: Copy-paste of a timer callback where the new method still references the old spec constant; refactoring that split one callback into two but kept the shared TimerSpec; wanting both watermark and realtime timers but reusing one spec instead of creating two.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


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

Appendix: source

Thrown at sdks/python/apache_beam/transforms/userstate.py:239

  This decorator allows a user to specify an on_timer processing method
  in a stateful DoFn.  Sample usage::

    class MyDoFn(DoFn):
      TIMER_SPEC = TimerSpec('timer', TimeDomain.WATERMARK)

      @on_timer(TIMER_SPEC)
      def my_timer_expiry_callback(self):
        logging.info('Timer expired!')
  """

  if not isinstance(timer_spec, TimerSpec):
    raise ValueError('@on_timer decorator expected TimerSpec.')

  def _inner(method: CallableT) -> CallableT:
    if not callable(method):
      raise ValueError('@on_timer decorator expected callable.')
    if timer_spec._attached_callback:
      raise ValueError(
          'Multiple on_timer callbacks registered for %r.' % timer_spec)
    timer_spec._attached_callback = method
    return method

  return _inner


def get_dofn_specs(dofn: 'DoFn') -> tuple[set[StateSpec], set[TimerSpec]]:
  """Gets the state and timer specs for a DoFn, if any.

  Args:
    dofn (apache_beam.transforms.core.DoFn): The DoFn instance to introspect for
      timer and state specs.
  """

  # Avoid circular import.
  from apache_beam.runners.common import MethodWrapper
  from apache_beam.transforms.core import _DoFnParam

View on GitHub (pinned to 12126d8942)