"Buzzers Over Blocking: Reactive Java Explained with Burgers and Mutiny" with Markus Eisele - JVM Weekly vol. 136
Why learning Mutiny with Quarkus will change how you think about threads, performance, and scalability in modern Java applications
This issue is sponsored by Bufstream - an innovative Kafka alternative focusing on Protobuf schema validation at the broker level. They invite you to a free workshop on July 10 to learn more about this technology.
Last May, I announced that JVM Weekly had joined the Foojay.io family. Foojay.io is a dynamic, community-driven platform for OpenJDK users, primarily Java and Kotlin enthusiasts. As a hub for the “Friends of OpenJDK,” Foojay.io gathers a rich collection of articles written by industry experts and active community members, offering valuable insights into the latest trends, tools, and practices within the OpenJDK ecosystem.
Just like last month, I’ve got something special for you - a fresh reprint of a JVM article from foojay.io. This time the keyboard belongs to
- a Munich-based Java Champion, long-time JVM evangelist and author of . The article uses Mutiny and Quarkus as its narrative hook, yet instead of pitching a product it offers a tour of just how hair-raisingly complex designing reactive systems at massive scale can be when millions of rows and thousands of nodes are on the line. Despite the fact that Reactive programming seems like something that went out of favour recently, I still consider it pretty useful, even in 2025.Buzzers Over Blocking: Reactive Java Explained with Burgers and Mutiny
Picture this: you walk into a restaurant and order a burger. Instead of sitting down, you’re required to stand there, eyes glued to the kitchen, unable to move, text, or talk to friends until your order is ready.
Oh, and the line behind you? It can’t move either. The cashier? Also stuck.
That’s how traditional blocking Java works.
// This is blocking code
String meal = kitchen.prepareMeal("burger"); // Stand and wait... and wait...
eat(meal); // Finally! Now you can eat
Every thread in your app becomes a customer frozen at the counter. Whether it's waiting on a slow database, network response, or I/O operation, the effect is the same: wasteful thread usage, resource contention, and scaling bottlenecks.
Welcome to the Reactive Restaurant: Mutiny Style
Now imagine a different kind of restaurant. You place your order and get a buzzer. You’re free to relax, chat, browse memes, or sip a drink. When your meal is ready, the buzzer goes off and only then do you return to collect your food.
This is how reactive programming works with Mutiny in Quarkus:
// This is reactive code with Mutiny
Uni<String> mealPromise = kitchen.prepareMeal("burger"); // Get buzzer
mealPromise
.onItem().invoke(meal -> eat(meal)) // When buzzer rings, eat!
.onFailure().invoke(error -> orderPizza()); // If kitchen explodes, improvise!
This model doesn't tie up threads. It makes your application responsive and resource-efficient. Even under heavy load.
The Magic Behind Mutiny: Uni & Multi
Mutiny provides two essential types:
Uni<T>:
A promise that emits one item (or failure).Multi<T>
: A stream that emits zero or more items (or failure).
You can think of:
Uni
: as a buzzer for your burger – one item, one notification.Multi
: as a series of appetizers – items arriving one after another.
📘 Read the full Mutiny guide for Uni and Multi
Real-World Scenarios, Restaurant Edition
Let’s walk through some fun parallels that reveal how Mutiny shines.
Scenario 1: The Slow Steak
// Traditional blocking: Your thread waits... doing nothing
String steak = kitchen.prepareSteak(); // Stand still for 20 mins
// Reactive approach: Place order and move on
Uni<String> steak = kitchen.prepareSteak();
steak.onItem().invoke(s -> enjoy(s)); // Get notified when ready
In reactive style, your thread is free to serve others while the steak cooks.
Scenario 2: The Appetizer Stream
// Reactive stream of appetizers
Multi<String> appetizers = kitchen.prepareAppetizers();
appetizers.onItem().invoke(app -> {
System.out.println("Yum! Got: " + app);
share(app); // Share with friends!
});
With Multi
, Mutiny handles streaming scenarios, such as reading from Kafka, SSE, or chunked file streams.
Scenario 3: The Kitchen Disaster
Uni<String> meal = kitchen.prepareMeal("pasta");
meal
.onItem().invoke(pasta -> eat(pasta))
.onFailure().invoke(error -> {
System.out.println("Kitchen burned down! Ordering pizza...");
orderPizza();
});
With .onFailure()
, you gracefully recover from failures without crashing the whole service. Another key benefit of reactive design.
Let’s Talk Real Java: Why This Actually Matters
The restaurant metaphor is a great mental model, but how does it translate to actual Java applications? Let’s walk through the real-world parallels between restaurant operations and application behavior to ground these concepts in your daily work as a developer.
When you place an order at a restaurant, you’re essentially making a request: Much like a Java service or database call. In a traditional blocking setup, placing that order means the customer (your thread) must stand there doing nothing until the kitchen (your backend) finishes preparing the response. That kitchen might be slow or overloaded, but your thread can’t proceed until the meal is done. This is what happens in synchronous Java code: the thread blocks until the operation completes.
In a reactive restaurant, placing an order gives the customer a buzzer, freeing them to do other things. Similarly, in reactive Java applications built with Mutiny and Quarkus, you get a Uni
or Multi
, a non-blocking placeholder for a future result. It lets your thread continue serving other users or handling other events while the work proceeds in the background. When the operation completes, your code reacts to the result.
The buzzer system also maps neatly to how backpressure and event-driven design work in Mutiny. Just like multiple chefs can work on different orders in parallel, your application can initiate multiple async operations simultaneously. And just like a cashier in a reactive restaurant isn’t held up by one slow customer, your threads stay unblocked and responsive, even under high load.
What this means in practice is that you can serve thousands of concurrent requests without needing thousands of threads. Your infrastructure becomes leaner, more predictable, and easier to scale. Especially in microservice or serverless environments. The benefit isn’t just technical elegance; it’s operational performance. Your application behaves like a well-run restaurant at lunch rush, not a slow-moving queue where one person delays everyone behind them.
Here’s how things look in code:
Blocking Style: Threads Just Wait
User user = database.findUser(id); // Wait 100ms
Orders orders = orderService.getOrders(user.getId()); // Wait 200ms
String receipt = receiptService.generate(orders); // Wait 50ms
sendToCustomer(receipt);
Your thread just burned ~350ms doing nothing. Multiply that by 1000 concurrent users… and you’ve got trouble.
Reactive Style with Mutiny
database.findUser(id) // Buzzer #1
.onItem().transformToUni(user ->
orderService.getOrders(user.getId())) // Buzzer #2
.onItem().transformToUni(orders ->
receiptService.generate(orders)) // Buzzer #3
.onItem().invoke(receipt ->
sendToCustomer(receipt)); // Done!
Each step happens only when the previous operation completes, without ever blocking the thread. Meanwhile, that thread is free to handle other events—just like a server multitasking in a busy restaurant.
Why Java Devs Should Care
Mutiny isn't just a clever abstraction. It's the backbone of Quarkus’s reactive core, enabling:
Scalable microservices that handle thousands of concurrent requests
Faster APIs by eliminating thread starvation
Better user experience with streamed responses and real-time updates
And best of all, it’s developer-friendly. No need to wrestle with Reactive Streams or callbacks. You get fluent, readable APIs with excellent failure handling and transformation chaining.
Final Thoughts: Buzzers Over Blocking
Traditional blocking code feels natural until your app needs to scale. Then it becomes a bottleneck.
Reactive programming with Mutiny and Quarkus gives Java developers the ability to write efficient, modern code that handles concurrency gracefully and predictably.
So next time you’re designing a service, ask yourself:
“Am I standing at the counter like a statue… or living my life with a buzzer in hand?”
More Resources
If the piece resonated with you, I highly recommend subscribing to
newsletter on Substack - you’ll get regular, code-heavy deep dives that build on themes like large-scale reactive design, but also AI. And for an even broader perspective, keep an eye on his forthcoming O’Reilly book Applied AI for Enterprise Java Development (currently in early release and co-authored with Alex Soto Bueno and Natale Vinto), which distils practical patterns for bringing generative AI and LLMs into the Java stack without vendor lock-in - with a bit more structured way than pile-up of different blog articles.And now, let's review some of the other cool things that appeared on Foojay.io last month.
Foojay Podcast #74: JCON Report, Part 3 – AI, ChatGPT, LLM, ML, RAG, MCP, GenAI, and more!
pisode 74 of the Foojay Podcast wraps up JCON 2025 coverage with a 50-minute “AI Bingo” (as Frank Delporte - host himself - named it) featuring eight rapid-fire chats:
Pasha Finkelshteyn kicks things off with tips on using retrieval-augmented generation (RAG) and Microsoft Cloud Platform
Simone de Gijt shares how she gets started with large-language-model (LLM) experiments
Steve Poole warns about AI’s hidden dangers
Sandra Ahlgrimm demos LangChain4j plus Azure tools
Mary Grygleski compares Spring AI, LangChain4j and Quarkus in real-world projects
Jonathan Vila 🥑 ☕️ links Sonar and Infrastructure-as-Code to AI risk management; Simon Martinelli predicts chat-driven UIs and clarifies MCP
Emily Jiang weighs Java versus Python for LLM work.
Throughout, the guests spotlight frameworks, security pitfalls and productivity wins, showing how Java developers can ride the GenAI wave without ditching their toolchain.
Authoring an OpenRewrite Recipe
In Authoring an OpenRewrite Recipe Nicolas Fränkel 🇺🇦🇬🇪 created a primer on what OpenRewrite actually is - an engine-plus-recipe ecosystem that automates framework upgrades, security fixes and style clean-ups - and argues that its real super-power is letting you craft your own refactorings rather than just running pre-baked ones. He explains that recipes are Visitor-based transformations applied to a Lossless Semantic Tree, then frames his personal use-case: Kotlin encourages flatter source trees than Java, so he wants a recipe that relocates classes, trimming away the common root package while keeping the package declaration intact.
Nicolas shows how to exercise the recipe with KotlinParser
inside JUnit-parameterised tests, asserts the new path, and sketches future enhancements. The takeaway is a concise pattern for authoring and validating OpenRewrite recipes that tidy even sprawling monorepos without manual getting through mud.
Faster Java Warmup: CRaC versus ReadyNow
Frank Delporte’s post Faster Java Warmup: CRaC versus ReadyNow opens by spelling out the hidden cost of every JVM launch: after the time-to-first-response the byte-code still has to “learn” and be re-compiled twice before your service really flies, and even then a wrong branch prediction can force a painful de-optimisation round-trip.
In the text Delporte compares two different answers that kill warm-up in opposite ways. CRaC snapshots an already-hot JVM and restores it later, which is near-instant but asks you to code for checkpoint/restore hygiene and match hardware when you redeploy. ReadyNow, by contrast, logs the compiler’s past decisions and replays them on the next run; no code changes, just a profile file and a few JVM flags, though you still pay class-loading time and need a “training” run to make the profile worth its salt
The interesting takeaway is pragmatic: Delporte isn’t selling magic bullets so much as offering a decision matrix - choose CRaC when you can modify code and need sub-second cold starts, choose ReadyNow when you’re stuck with black-box apps but can tolerate a short class-load pause - giving Java devs a clear mental model rather than yet another vendor pitch.
Java Concurrency Best Practices for MongoDB
Vivekanandan Sakthivelu in his Foojay post Java Concurrency Best Practices for MongoDB charts the four classic headaches that appear when multiple Java threads poke the same MongoDB data—lost updates, dirty reads, non-repeatable reads and phantom reads—illustrating each with concise, copy-paste code so readers can reproduce the symptoms on a local replica set.
He then shows how to defuse every race by flipping a few driver-level switches instead of writing DIY locks: atomic operators such as $inc
fix update clashes, readConcern:"majority"
keeps uncommitted data out of view, snapshot transactions stamp out repeatable-read and phantom woes, and the right writeConcern
level balances durability versus latency.
And that’s all, folks! I hope it was tasty meal 😁