LMAX-Exchange/disruptor · error · IllegalStateException
Disruptor.start() must only be called once.
Error message
Disruptor.start() must only be called once.
What it means
Thrown by Disruptor.checkOnlyStartedOnce when start() is invoked a second time on the same Disruptor instance. The started flag is flipped with compareAndSet(false, true); a successful start is a one-shot transition because consumer threads and sequences are already live and re-starting would duplicate them.
Source
Thrown at src/main/java/com/lmax/disruptor/dsl/Disruptor.java:604
eventProcessors[i] = processorFactories[i].createEventProcessor(ringBuffer, barrierSequences);
}
return handleEventsWith(eventProcessors);
}
private void checkNotStarted()
{
if (started.get())
{
throw new IllegalStateException("All event handlers must be added before calling starts.");
}
}
private void checkOnlyStartedOnce()
{
if (!started.compareAndSet(false, true))
{
throw new IllegalStateException("Disruptor.start() must only be called once.");
}
}
@Override
public String toString()
{
return "Disruptor{" +
"ringBuffer=" + ringBuffer +
", started=" + started +
", threadFactory=" + threadFactory +
'}';
}
}
View on GitHub (pinned to c871ca4982)
Solutions
- Ensure start() is called exactly once: guard with your own AtomicBoolean or synchronise the init path.
- In Spring, use a single lifecycle hook (e.g. SmartLifecycle.start()) for the Disruptor.
- For restart scenarios, build a new Disruptor instance after shutdown() rather than restarting the old one.
Example fix
// before
void init() { disruptor.handleEventsWith(h); disruptor.start(); }
// called twice (e.g. @PostConstruct + manual) -> IllegalStateException
// after
private final AtomicBoolean started = new AtomicBoolean();
void init() {
disruptor.handleEventsWith(h);
if (started.compareAndSet(false, true)) disruptor.start();
} Defensive patterns
Strategy: validation
Validate before calling
private final AtomicBoolean startedOnce = new AtomicBoolean();
void startDisruptor() {
if (startedOnce.compareAndSet(false, true)) {
disruptor.start();
}
} Prevention
- Own the start call in exactly one lifecycle method (e.g. SmartLifecycle.start()).
- For restarts, create a fresh Disruptor after shutdown() instead of reusing the instance.
When it happens
Trigger: Calling disruptor.start() twice — commonly a restart-on-failure routine, multiple components each owning a 'start the disruptor' step, or a retry that re-enters the initializing method after a partial failure.
Common situations: Spring lifecycle where both @PostConstruct and an ApplicationListener/SmartLifecycle trigger start(); reconnect/recovery code that calls the same init method again; unit tests reusing one static Disruptor across test methods.
Related errors
- Thread is already running
- All event handlers must be added before calling starts.
- setDefaultExceptionHandler can not be used after handleExcep
- Already running
- bufferSize must not be less than 1
AI-assisted analysis of LMAX-Exchange/disruptor@c871ca4982 (2026-08-14).
Data as JSON: /api/errors/5de8f91d56052776.
Report an issue: GitHub.