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
- Create a separate TimerSpec for each callback: TIMER_SPEC_A, TIMER_SPEC_B, each with its own @on_timer decorator
- Remove the duplicate @on_timer registration so each spec has exactly one callback
- 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
- One TimerSpec per expiry callback; create distinct specs for distinct methods
- Never copy-paste a @on_timer(SPEC) block without changing the spec constant
- Use different TimeDomains intentionally with separate specs
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
- @on_timer decorator expected callable.
- @on_timer decorator expected TimerSpec.
- DoFn %r has a TimerSpec without an associated on_timer…
- DoFn %r has multiple TimerSpecs with the same name
- NotImplementedError
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 _DoFnParamView on GitHub (pinned to 12126d8942)