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

  1. Make the custom ThreadFactory always return a non-null Thread; delegate to Executors.defaultThreadFactory().newThread(runnable) and then rename/customize the returned thread.
  2. 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)))).
  3. 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.
  4. 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

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


AI-assisted analysis of LMAX-Exchange/disruptor@c871ca4982 (2026-08-14). Data as JSON: /api/errors/6046b1631ab1e1f2. Report an issue: GitHub.