{"record":{"id":"5de8f91d56052776","repo":"LMAX-Exchange/disruptor","slug":"disruptor-start-must-only-be-called-once","errorCode":null,"errorMessage":"Disruptor.start() must only be called once.","messagePattern":"Disruptor\\.start\\(\\) must only be called once\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/lmax/disruptor/dsl/Disruptor.java","lineNumber":604,"sourceCode":"            eventProcessors[i] = processorFactories[i].createEventProcessor(ringBuffer, barrierSequences);\n        }\n\n        return handleEventsWith(eventProcessors);\n    }\n\n    private void checkNotStarted()\n    {\n        if (started.get())\n        {\n            throw new IllegalStateException(\"All event handlers must be added before calling starts.\");\n        }\n    }\n\n    private void checkOnlyStartedOnce()\n    {\n        if (!started.compareAndSet(false, true))\n        {\n            throw new IllegalStateException(\"Disruptor.start() must only be called once.\");\n        }\n    }\n\n    @Override\n    public String toString()\n    {\n        return \"Disruptor{\" +\n                \"ringBuffer=\" + ringBuffer +\n                \", started=\" + started +\n                \", threadFactory=\" + threadFactory +\n                '}';\n    }\n}\n","sourceCodeStart":586,"sourceCodeEnd":618,"githubUrl":"https://github.com/LMAX-Exchange/disruptor/blob/c871ca49826a6be7ada6957f6fbafcfecf7b1f87/src/main/java/com/lmax/disruptor/dsl/Disruptor.java#L586-L618","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nvoid init() { disruptor.handleEventsWith(h); disruptor.start(); }\n// called twice (e.g. @PostConstruct + manual) -> IllegalStateException\n\n// after\nprivate final AtomicBoolean started = new AtomicBoolean();\nvoid init() {\n    disruptor.handleEventsWith(h);\n    if (started.compareAndSet(false, true)) disruptor.start();\n}","handlingStrategy":"validation","validationCode":"private final AtomicBoolean startedOnce = new AtomicBoolean();\nvoid startDisruptor() {\n    if (startedOnce.compareAndSet(false, true)) {\n        disruptor.start();\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["disruptor","lifecycle","illegal-state","double-start","initialization"],"backgroundTag":null,"analyzedSha":"c871ca49826a6be7ada6957f6fbafcfecf7b1f87","analyzedAt":"2026-08-14T14:22:36.358Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}