LMAX Disruptor v4.0.0

Posted on Feb 5, 2026
Disruptor logo

If you’ve read this blog before, you may have noticed a cluster of posts from 2013-2016, followed by a suspiciously long silence until late 2025. The main reason for that gap was a job change in mid-2017, when I left Fivium and joined LMAX Exchange Group.

LMAX didn’t discourage blogging - quite the opposite. But the work demanded more focus, the commute was a lot longer, and life outside work also became busier. Something had to give, and unfortunately that something was writing. Lately I’ve been trying to get back into it, which has involved reflecting on some of the more interesting things I’ve worked on over the last decade.

The LMAX Disruptor is one of LMAX’s more visible contributions to the open-source Java ecosystem. I maintained it for a period during my time at LMAX Exchange Group, primarily focusing on correctness, performance validation, and modernising it for newer Java runtimes.

A Brief History of the LMAX Disruptor

LMAX open-sourced the Disruptor in June 2011 as a high-performance alternative to bounded queues for inter-thread communication.

It was a central component of what Martin Fowler described as the LMAX Architecture: single-threaded business logic fed by a single inbound queue, with downstream consumers processing results asynchronously.

At a conceptual level, this resembles the Actor Model which has been around since the 1970s, but actor-style systems built on conventional queues struggle in low-latency, low-jitter environments. Queue contention, memory allocation, and cache-line bouncing often become the bottleneck.

The Disruptor was designed to address this by combining:

  • A fixed-size circular buffer
  • Explicit sequencing rather than implicit locking
  • Careful memory ordering aligned with CPU behaviour
  • A DSL for declaring consumer dependency graphs

While often described as a “ring buffer”, the buffer itself is only one part. The sequencing model and consumer graph are the defining features.

Release History

Despite the long gaps between major versions, the core design has remained stable since the initial release.

My Maintainership

By 2018 the Disruptor was sitting at v3.4.2 and had been for some time. Mostly because it worked.

Internally, LMAX still used it extensively in production systems, but it wasn’t under active feature development. Around 2019 the previous maintainer left LMAX, leaving the GitHub repository in a kind of collective ownership limbo at the hands of everyone in the dev team. Issues and PRs accumulated, everyone quietly hoping someone else would deal with them.

After about a year of this, helped along by the surplus evening and weekend time provided by COVID lockdowns, I started chipping away at it. I began triaging old issues, closing stale PRs, modernising the build and CI setup, and gradually formed a plan for what I actually wanted to change.

The goal was simple: make the Disruptor comfortable in a post-JDK-11 world, where sun.misc.Unsafe was finally becoming something you couldn’t rely on forever.

Documentation

Simon, a colleague at LMAX, introduced the project to AsciiDoc and migrated the documentation.

Previously, the docs consisted of a PDF written for v1.0 and a Microsoft Word .docx file.
Yes, really. 😬

The new documentation lives entirely in AsciiDoc and is built and published automatically to a browsable website on every commit. A massive improvement.

Building and Testing

Before this cleanup, the project relied on a personal Semaphore CI account belonging to the previous maintainer. That had been the case since 2017, which was… not ideal.

GitHub Actions launched publicly in late 2018, and once again Simon led the way by adding workflows to build and publish the documentation. I later extended these to build and publish artifacts and to run the test suite on every commit.

Nothing fancy, but solid, visible, and owned by the project rather than an individual.

Performance Testing

The Disruptor has included performance tests since v3 in ~2013, but historically these were just Java classes with main methods, ad-hoc loops, and timing via System.currentTimeMillis(). 😬

Since 2014, Java Microbenchmark Harness (JMH) has been the correct tool for this job, so I added a small set of focused JMH benchmarks targeting specific components such as the sequence and ring buffer.

These were especially useful when making non-functional changes, like comparing sun.misc.Unsafe with VarHandle, or experimenting with explicit memory fences.

The benchmarks aren’t run in CI. Shared GitHub runners are too noisy and inconsistent for meaningful performance results, and self-hosted runners in public repos are a security footgun. Instead, I ran them locally on a tuned machine and attached results directly to PRs to justify changes, for example in the PR that removed Unsafe.

They were also handy for explaining my stance on performance trade-offs directly in PR discussions and issues.

JCStress

The Disruptor is, at its core, a Java concurrency primitive. Concurrent code is a nightmare to test. For years, there was nothing that really validated the correctness of its memory-ordering guarantees.

In 2017 the Java Concurrency Stress (jcstress) framework was released, which is designed for exactly this. It doesn’t give you proofs, but it can give you confidence if you design the tests carefully.

While migrating away from sun.misc.Unsafe, I initially made a subtle mistake. All unit tests passed. The JMH benchmarks looked fine. Even the JCStress tests passed. But only on x86.

Because JCStress was in place, I ran the tests on an ARM machine (a Raspberry Pi 3 Model B), where they promptly failed. That discovery is documented here.

There’s a future blog post in this somewhere, probably more than one.

Removing WorkerPool

WorkerPool was introduced in Disruptor v3 but was never used internally at LMAX. As a result, we weren’t particularly good maintainers of it, despite it accounting for a sizeable chunk of the codebase.

The functionality could be recreated easily in user code, so removing it from the core library felt like the right call, even if it was mildly controversial.

Batch Size Limiting

BatchEventProcessor::processEvents batches events for efficiency, but in some situations you want to cap batch sizes so that sequences advance more frequently under backlog.

LMAX already had this internally, so the feature was ported to the open-source Disruptor.

Batch Rewind

This feature was originally added internally by Jamie, another LMAX colleague, and later ported to open source.

It allows an EventHandler to throw a RewindableException, signalling that the batch should be retried (or not) based on a BatchRewindStrategy. The details are covered in the user guide.

Event Handler Interfaces

Targeting JDK 11+ meant we could finally use default methods on interfaces to simplify a lot of runtime checks and casting.

While doing this work, I spotted a fairly nasty footgun around Batch Rewind that could affect both internal LMAX code and external users. Rather than fixing it with runtime validation, I found a way to make problematic code simply not compile by leaning on the type system. 🎉

This caused some migration pain moving from v3 to v4, but that’s exactly what major versions are for.

Replying to Issues and PRs

This was my first experience maintaining an open-source project with any real level of usage.

The Disruptor is niche, but it’s used seriously, and that brings issues, PRs, and opinions from the general public.

This was mostly before AI slop and vibe coding came to the fore, but we still got plenty of low-value PRs clearly raised just to appear as a contributor. We also saw regular complaints about CPU usage, shutdown speed, and similar non-issues for the low-latency systems the library was built for. Most of those ended in a polite wont-fix.

That said, there were also genuinely excellent contributors, often people maintaining Disruptor implementations in other languages, like C#. Their feedback was thoughtful and valuable, and it actually improved the project. Thank you to them.

Release

Finally, in September 2023 I released Disruptor v4.0.0. 🎉

Future Work

Sadly I left LMAX in mid-2024 after seven happy years there. Upon leaving I was relieved of my maintainer duties, and current LMAX developers have since taken over. They’re available to answer sensibly raised issues and PRs.

Whether there will ever be a v5 is anyone’s guess. Given that v4 landed after a ten-year gap, perhaps we should all check again in 2033?