LMAX-Exchange/disruptor · error · RuntimeException
Failed to create thread to run: {}
Error message
Failed to create thread to run: {} What it means
Thrown by EventProcessorInfo.start (src/main/java/com/lmax/disruptor/dsl/EventProcessorInfo.java:71) when the ThreadFactory supplied to Disruptor.start(threadFactory) returns null from newThread(). The Disruptor DSL starts each event processor by asking the factory for a thread and calling thread.start(); a null return is treated as a broken factory configuration. It is an unchecked guard against misconfigured thread creation, not a transient failure.
Source
Thrown at src/main/java/com/lmax/disruptor/dsl/EventProcessorInfo.java:71
@Override
public SequenceBarrier getBarrier()
{
return barrier;
}
@Override
public boolean isEndOfChain()
{
return endOfChain;
}
@Override
public void start(final ThreadFactory threadFactory)
{
final Thread thread = threadFactory.newThread(eventprocessor);
if (null == thread)
{
throw new RuntimeException("Failed to create thread to run: " + eventprocessor);
}
thread.start();
}
@Override
public void halt()
{
eventprocessor.halt();
}
/**
*
*/
@Override
public void markAsUsedInBarrier()
{
endOfChain = false;View on GitHub (pinned to c871ca4982)
Solutions
- Make the custom ThreadFactory always return a non-null Thread; delegate to Executors.defaultThreadFactory().newThread(runnable) and then rename/customize the returned thread.
- If the factory came from a DI container or mock, fix the wiring/stubbing so newThread returns a real Thread (e.g. Mockito when(factory.newThread(any())).thenAnswer(inv -> new Thread(inv.getArgument(0)))).
- Audit every return path (including error paths) of the factory lambda for accidental null returns; prefer throwing inside the factory if creation genuinely fails instead of returning null.
- As a stopgap, use disruptor.start() with no factory (BasicValidator/Disruptor uses Executors.defaultThreadFactory()) to confirm the rest of the wiring is correct before re-adding the custom factory.
Example fix
// before
ThreadFactory tf = r -> shouldCreateThreads ? new Thread(r) : null; // null branch -> RuntimeException at EventProcessorInfo.start
disruptor.start(tf);
// after
ThreadFactory tf = r -> {
Thread t = new Thread(r);
t.setName("disruptor-processor");
t.setDaemon(true);
return t; // always non-null
};
disruptor.start(tf); Defensive patterns
Strategy: validation
Validate before calling
// Before disruptor.start(threadFactory): verify the factory returns a non-null thread
Thread probe = threadFactory.newThread(() -> {});
if (probe == null) {
throw new IllegalStateException("ThreadFactory must return a non-null Thread");
} Try / catch
// If start() is invoked with externally supplied factories, fail fast with context
try {
disruptor.start(threadFactory);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Failed to create thread")) {
throw new IllegalStateException("Disruptor ThreadFactory returned null: " + threadFactory, e);
}
throw e;
} Prevention
- Always delegate custom ThreadFactory implementations to Executors.defaultThreadFactory() and only decorate the returned thread (name, daemon, priority).
- Never write a null-returning branch in a ThreadFactory; throw on failure instead of returning null.
- In tests, stub newThread explicitly: when(factory.newThread(any())).thenAnswer(inv -> new Thread(inv.getArgument(0)));
When it happens
Trigger: Calling disruptor.start(customThreadFactory) where customThreadFactory.newThread(runnable) returns null (e.g. a factory that conditionally creates threads, wraps Executors.defaultThreadFactory() but forwards a null field, or a factory whose backing pool is shut down and coded to return null). Also passing a mocked ThreadFactory in tests that is not stubbed for newThread (Mockito default returns null).
Common situations: Custom ThreadFactory for naming/daemon threads that has a null-returning branch; unit tests of the DSL wiring with an un-stubbed mock factory; dependency-injection setups where the factory bean is optional and resolves to null-returning lambda; upgrading code that previously used the no-arg start() and later passes a hand-written factory.
Related errors
- value must be a positive number
- bufferSize must not be less than 1
- bufferSize must be a power of 2
- maxBatchSize must be greater than 0
- batchRewindStrategy cannot be null when building a BatchEven
AI-assisted analysis of LMAX-Exchange/disruptor@c871ca4982 (2026-08-14).
Data as JSON: /api/errors/6046b1631ab1e1f2.
Report an issue: GitHub.