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

  1. Ensure start() is called exactly once: guard with your own AtomicBoolean or synchronise the init path.
  2. In Spring, use a single lifecycle hook (e.g. SmartLifecycle.start()) for the Disruptor.
  3. 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

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


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