apache/beam · error · Error

Not expecting timers.

Error message

Not expecting timers.

What it means

The TypeScript worker's source/operator startBundle wires a sendTimers callback that unconditionally throws. This operator type does not expect timers to be emitted, so receiving them signals an internal invariant violation in bundle execution.

Solutions

  1. Avoid timers in transforms run by this operator; restructure to use windowing-only behavior.
  2. Upgrade the apache-beam TypeScript SDK to a version supporting timers if available.
  3. Move the timer-dependent transform to the Python or Java SDK.

Example fix

// before
context.setTimer("fire", window, Duration.millis(0));
// after
// emit directly or rely on window triggers instead of timers
Defensive patterns

Strategy: validation

Validate before calling

// assert transforms don't call setTimer before submitting to the TS worker
if (transformRequiresTimers(dofn)) throw new Error("Refactor: timers unsupported here");

Try / catch

try {
  await worker.startBundle();
} catch (e) {
  if (e.message === "Not expecting timers.") {
    // redesign pipeline without timers or switch SDK
  }
}

Prevention

When it happens

Trigger: During a bundle, the user transform (or windowing logic) emits a timer while running under this operator whose sendTimers is a throw-stub.

Common situations: Stateful/timer-using DoFns executed by the TypeScript worker; combining windowing features with an operator that doesn't support timer output; SDK feature gap.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at sdks/typescript/src/apache_beam/worker/operators.ts:244

            this_.lastProcessedElement += 1;
            const maybePromise = this_.receiver.receive(
              this_.coder.decode(reader, CoderContext.needsDelimiters),
            );
            if (maybePromise !== NonPromise) {
              await maybePromise;
            }
            // Periodically yield control explicitly to allow other tasks
            // (including splits requests) to get scheduled.
            // Note that waiting on a resolved promise is not sufficient, so
            // we do this in addition to the above loop.
            if (Date.now() - lastYield > 100 /* milliseconds */) {
              await new Promise((r) => setTimeout(r, 0));
              lastYield = new Date().getTime();
            }
          }
        },
        sendTimers: async function (timerFamilyId: string, timers: Uint8Array) {
          throw Error("Not expecting timers.");
        },
        close: function () {
          endOfDataResolve();
        },
        onError: function (error: Error) {
          endOfDataReject(error);
        },
      },
    );
  }

  process(wvalue: WindowedValue<unknown>): ProcessResult {
    throw Error("Data should not come in via process.");
  }

  split(
    desiredSplit: fnApi.ProcessBundleSplitRequest_DesiredSplit,
  ): fnApi.ProcessBundleSplitResponse_ChannelSplit | undefined {

View on GitHub (pinned to 12126d8942)