LMAX-Exchange/disruptor · error · IllegalStateException
Thread is already running
Error message
Thread is already running
What it means
Thrown by BatchEventProcessor.run() when it observes the processor is already in the RUNNING state. The processor guards its run loop with an atomic state field; run() is designed to be executed exactly once per processor lifecycle (usually by a single thread from an ExecutorService), and re-invoking it while alive is a lifecycle bug.
Source
Thrown at src/main/java/com/lmax/disruptor/BatchEventProcessor.java:135
notifyStart();
try
{
if (running.get() == RUNNING)
{
processEvents();
}
}
finally
{
notifyShutdown();
running.set(IDLE);
}
}
else
{
if (witnessValue == RUNNING)
{
throw new IllegalStateException("Thread is already running");
}
else
{
earlyExit();
}
}
}
private void processEvents()
{
T event = null;
long nextSequence = sequence.get() + 1L;
while (true)
{
final long startOfBatchSequence = nextSequence;
try
{View on GitHub (pinned to c871ca4982)
Solutions
- Do not call run() yourself — let Disruptor start the processor via handleEventsWith(...) and its internal executor.
- If you manage threads, submit each BatchEventProcessor exactly once; to restart, build a new processor (and re-wire it) after halt().
- Check for code that retries failed tasks by resubmitting the same Runnable.
Example fix
// before executor.submit(batchEventProcessor); // later, after a hiccup: executor.submit(batchEventProcessor); // IllegalStateException // after // one processor per lifecycle; to restart, create a new one batchEventProcessor.halt(); BatchEventProcessor<MyEvent> fresh = new BatchEventProcessorBuilder().build(...); executor.submit(fresh);
Defensive patterns
Strategy: validation
Validate before calling
if (batchEventProcessor.isRunning()) {
// do not call run()/resubmit; halt and rebuild instead
} Try / catch
try {
executor.submit(processor);
} catch (IllegalStateException e) {
if ("Thread is already running".equals(e.getMessage())) { /* processor already live; log and skip */ }
else throw e;
} Prevention
- Let Disruptor's DSL own processor start; never call run() manually.
- Track submitted processors in a set to make double-submission impossible.
When it happens
Trigger: Calling run() manually on a processor that the Disruptor framework has already started; submitting the same BatchEventProcessor instance to an executor twice; wrapping the processor in your own retry/restart loop without halting it first.
Common situations: Custom wiring where the developer manages threads instead of letting Disruptor's handleEventsWith start them; restarting a 'stuck' consumer by re-running the same object; accidentally sharing one processor across two worker threads.
Related errors
- All event handlers must be added before calling starts.
- Disruptor.start() must only be called once.
- Already running
- maxBatchSize must be greater than 0
- setDefaultExceptionHandler can not be used after handleExcep
AI-assisted analysis of LMAX-Exchange/disruptor@c871ca4982 (2026-08-14).
Data as JSON: /api/errors/63ac3b2a7fe6fc70.
Report an issue: GitHub.