apache/beam · error · IllegalStateException
Outbound timers endpoint already registered for ${timerKey}
Error message
Outbound timers endpoint already registered for ${timerKey} What it means
Analogous to the data endpoint check: registerOutputTimersLocation registers one outbound timers receiver per (pTransformId, timerFamilyId) pair. This IllegalStateException is thrown when the same TimerEndpoint is registered twice, which would mean two receivers competing for the same timers channel.
Source
Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/fn/data/BeamFnDataOutboundAggregator.java:168
checkFlushThreadException();
synchronized (flushLock) {
receiver.accept(data);
}
};
}
outputDataReceivers.put(pTransformId, receiver);
return receiver;
}
/**
* Register the outbound timers logical endpoint, returns the FnDataReceiver for processing the
* endpoint's outbound timers data.
*/
public <T> FnDataReceiver<T> registerOutputTimersLocation(
String pTransformId, String timerFamilyId, Coder<T> coder) {
TimerEndpoint timerKey = new TimerEndpoint(pTransformId, timerFamilyId);
if (outputTimersReceivers.containsKey(timerKey)) {
throw new IllegalStateException(
"Outbound timers endpoint already registered for " + timerKey);
}
Receiver<T> receiver = new Receiver<>(coder);
if (timeLimit > 0) {
outputTimersReceivers.put(timerKey, receiver);
return timers -> {
checkFlushThreadException();
synchronized (flushLock) {
receiver.accept(timers);
}
};
}
outputTimersReceivers.put(timerKey, receiver);
return receiver;
}
private void flushInternal() {
if (bytesWrittenSinceFlush == 0) {View on GitHub (pinned to 12126d8942)
Solutions
- Ensure each (pTransformId, timerFamilyId) pair is registered only once per bundle
- Verify the timer family id is distinct where multiple timers per transform are intended
- Check bundle setup code for double invocation of addOutgoingTimersEndpoint
- Recreate the aggregator between bundles so stale registrations are cleared
Example fix
// before aggregator.registerOutputTimersLocation(pTransformId, timerFamilyId, coder); aggregator.registerOutputTimersLocation(pTransformId, timerFamilyId, coder); // throws // after aggregator.registerOutputTimersLocation(pTransformId, timerFamilyId + "-v2", coder); // distinct family id
Defensive patterns
Strategy: validation
Validate before calling
if (registeredTimerKeys.contains(pTransformId + "/" + timerFamilyId)) {
return; // skip duplicate registration
}
registeredTimerKeys.add(pTransformId + "/" + timerFamilyId); Try / catch
try {
aggregator.registerOutputTimersLocation(pTransformId, timerFamilyId, coder);
} catch (IllegalStateException e) {
LOG.warn("timer endpoint already registered: " + timerFamilyId, e);
} Prevention
- Use a unique timerFamilyId per timer declaration
- Register timers only in one-time bundle setup
- Keep a Set of registered TimerEndpoint keys
When it happens
Trigger: Calling registerOutputTimersLocation twice with identical pTransformId and timerFamilyId; duplicate wiring through addOutgoingTimersEndpoint or registerOutputLocation for the same timer family.
Common situations: Stateful DoFns with multiple timer families whose registration code runs twice per bundle; runner harness re-initialization without resetting outputTimersReceivers between bundles.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Outbound data endpoint already registered for
- Conflicting registrations for: %s
- typehint for arg type %s already exists
- Cannot access timer in non-window observing context.
- Cannot access timerFamily in non-window observing context.
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/275baea6f93386f3.
Report an issue: GitHub.