apache/beam · error · ValueError

The on_timer callback for

Error message

The on_timer callback for %s is not the specified .%s method for DoFn %r (perhaps it was overwritten?).

What it means

Apache Beam raises this ValueError when the TimerSpec's attached on_timer callback does not match the method of the same name resolved on the DoFn instance (getattr(dofn, method_name).__func__). This indicates the declared callback was overwritten, rebound, or is a different function than the one living on the class. The check runs in validate_stateful_dofn to catch subtle wiring bugs where a timer would silently fire the wrong handler.

Solutions

  1. Ensure the TimerSpec references the exact method defined on this DoFn class and that it is not overwritten later.
  2. If subclassing, redefine the TimerSpec in the subclass pointing at the subclass's callback.
  3. Remove decorators/monkey-patches that replace the bound on_timer function, or recreate the TimerSpec after the final method definition.

Example fix

// before
class MyDoFn(DoFn):
  T = TimerSpec('t', on_timer=on_timer)
  def on_timer(self): ...
MyDoFn.on_timer = lambda self: None  # overwrites -> mismatch
// after
class MyDoFn(DoFn):
  T = TimerSpec('t', on_timer=on_timer)
  def on_timer(self): ...  # never reassigned
Defensive patterns

Strategy: validation

Validate before calling

from apache_beam.transforms.userstate import get_dofn_specs
for t in get_dofn_specs(MyDoFn)[1]:
  cb = t._attached_callback
  assert cb == getattr(MyDoFn, cb.__name__, None), f'{t.name} callback not the class method'

Try / catch

try:
  validate_stateful_dofn(MyDoFn)
except ValueError as e:
  if 'perhaps it was overwritten' in str(e):
    inspect subclass overrides of the timer callback
  else:
    raise

Prevention

When it happens

Trigger: Assigning over the on_timer method after TimerSpec creation (e.g. subclass overrides, monkey-patching, or decorating the method after binding so the bound __func__ differs from the one captured in _attached_callback); validate_stateful_dofn compares timer_spec._attached_callback != getattr(dofn, method_name, None).__func__ and fails.

Common situations: Subclassing a DoFn and overriding the timer callback; applying decorators (e.g. wrappers) that replace the original function; dynamic attribute assignment on the DoFn class; copying a TimerSpec from another class where the callback belongs elsewhere.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

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

  if len(all_state_specs) != len(set(s.name for s in all_state_specs)):
    raise ValueError(
        'DoFn %r has multiple StateSpecs with the same name: %s.' %
        (dofn, all_state_specs))
  if len(all_timer_specs) != len(set(s.name for s in all_timer_specs)):
    raise ValueError(
        'DoFn %r has multiple TimerSpecs with the same name: %s.' %
        (dofn, all_timer_specs))

  # Reject DoFns that use timer specs without corresponding timer callbacks.
  for timer_spec in all_timer_specs:
    if not timer_spec._attached_callback:
      raise ValueError((
          'DoFn %r has a TimerSpec without an associated on_timer '
          'callback: %s.') % (dofn, timer_spec))
    method_name = timer_spec._attached_callback.__name__
    if (timer_spec._attached_callback != getattr(dofn, method_name,
                                                 None).__func__):  # type: ignore[union-attr]
      raise ValueError((
          'The on_timer callback for %s is not the specified .%s method '
          'for DoFn %r (perhaps it was overwritten?).') %
                       (timer_spec, method_name, dofn))


class BaseTimer(object):
  def clear(self, dynamic_timer_tag: str = '') -> None:
    raise NotImplementedError

  def set(self, timestamp: Timestamp, dynamic_timer_tag: str = '') -> None:
    raise NotImplementedError


_TimerTuple = collections.namedtuple('timer_tuple', ('cleared', 'timestamp'))  # type: ignore[name-match]


class RuntimeTimer(BaseTimer):
  """Timer interface object passed to user code."""

View on GitHub (pinned to 12126d8942)