BlogAnnounced at MongoDB.local NYC 2024: A recap of all announcements and updatesLearn more >>
MongoDB Developer
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right

Optimizing Java Performance With Virtual Threads, Reactive Programming, and MongoDB

Maxime Beugnet5 min read • Published Mar 20, 2024 • Updated Mar 20, 2024
Facebook Icontwitter iconlinkedin icon
Rate this article


When I first heard about Project Loom and virtual threads, my first thought was that this was a death sentence for reactive programming. It wasn't bad news at first because reactive programming comes with its additional layer of complexity and using imperative programming without wasting resources was music to my ears.
But I was actually wrong and a bit more reading and learning helped me understand why thinking this was a mistake.
In this post, we'll explore virtual threads and reactive programming, their differences, and how we can leverage both in the same project to achieve peak concurrency performance in Java.
Learn more about virtual threads support with MongoDB in my previous post on this topic.

Virtual threads

Traditional thread model in Java

In traditional Java concurrency, threads are heavyweight entities managed by the operating system. Each OS thread is wrapped by a platform thread which is managed by the Java Virtual Machine (JVM) that executes the Java code.
Each thread requires significant system resources, leading to limitations in scalability when dealing with a large number of concurrent tasks. Context switching between threads is also resource-intensive and can deteriorate the performance.

Introducing virtual threads

Virtual threads, introduced by Project Loom in JEP 444, are lightweight by design and aim to overcome the limitations of traditional threads and create high-throughput concurrent applications. They implement java.lang.Thread and they are managed by the JVM. Several of them can run on the same platform thread, making them more efficient to work with a large number of small concurrent tasks.

Benefits of virtual threads

Virtual threads allow the Java developer to use the system resources more efficiently and non-blocking I/O.
But with the closely related JEP 453: Structured Concurrency and JEP 446: Scoped Values, virtual threads also support structured concurrency to treat a group of related tasks as a single unit of work and divide a task into smaller independent subtasks to improve response time and throughput.


Here is a basic Java example.
Output of this program:
We can see that the tasks ran in parallel — each in a different virtual thread, managed by a single ForkJoinPool and its associated workers.

Reactive programming

First of all, reactive programming is a programming paradigm whereas virtual threads are "just" a technical solution. Reactive programming revolves around asynchronous and event-driven programming principles, offering solutions to manage streams of data and asynchronous operations efficiently.
In Java, reactive programming is traditionally implemented with the observer pattern.
The pillars of reactive programming are:
  • Non-blocking I/O.
  • Stream-based asynchronous communication.
  • Back-pressure handling to prevent overwhelming downstream components with more data than they can handle.
The only common point of interest with virtual threads is the first one: non-blocking I/O.

Reactive programming frameworks

The main frameworks in Java that follow the reactive programming principles are:


MongoDB also offers an implementation of the Reactive Streams API: the MongoDB Reactive Streams Driver.
Here is an example where I insert a document in MongoDB and then retrieve it.
Note: The SubscriberHelpers.OperationSubscriber and SubscriberHelpers.PrintDocumentSubscriber classes come from the Reactive Streams Quick Start Primer. You can find the in the MongoDB Java Driver repository code examples.

Virtual threads and reactive programming working together

As you might have understood, virtual threads and reactive programming aren't competing against each other, and they certainly agree on one thing: Blocking I/O operations is evil!
Who said that we had to make a choice? Why not use them both to achieve peak performance and prevent blocking I/Os once and for all?
Good news: The reactor-core library added virtual threads support in 3.6.0. Project Reactor is the library that provides a rich and functional implementation of Reactive Streams APIs in Spring Boot and WebFlux.
This means that we can use virtual threads in a Spring Boot project that is using MongoDB Reactive Streams Driver and Webflux.
There are a few conditions though:
  • Use Tomcat because — as I'm writing this post — Netty (used by default by Webflux) doesn't support virtual threads. See GitHub issues 12848 and 39425 for more details.
  • Activate virtual threads: spring.threads.virtual.enabled=true in

Let's test

In the repository, my colleague Wen Jie Teo and I updated the pom.xml and so we could use virtual threads in this reactive project.
You can run the following commands to get this project running quickly and test that it's running with virtual threads correctly. You can get more details in the file but here is the gist.
Here are the instructions in English:
  • Clone the repository and access the folder.
  • Update the log level in to info.
  • Start a local MongoDB single node replica set instance or use MongoDB Atlas.
  • Run the setup.js script to initialize the accounts collection.
  • Start the Java application.
  • Test one of the APIs available.
Here are the instructions translated into Bash.
First terminal:
Note: On macOS, you may have to use sed -i '' 's/warn/info/g' src/main/resources/ if you are not using gnu-sed, or you can just edit the file manually.
Second terminal
If everything worked as planned, you should see this line in the first terminal (where you are running Spring).
This is the last line in the stack trace that we are logging. It proves that we are using virtual threads to handle our query.
If we disable the virtual threads in the file and try again, we'll read instead:
This time, we are using a classic java.lang.Thread instance to handle our query.


Virtual threads and reactive programming are not mortal enemies. The truth is actually far from that.
The combination of virtual threads’ advantages over standard platform threads with the best practices of reactive programming opens up new frontiers of scalability, responsiveness, and efficient resource utilization for your applications. Be gone, blocking I/Os!
MongoDB Reactive Streams Driver is fully equipped to benefit from both virtual threads optimizations with Java 21, and — as always — benefit from the reactive programming principles and best practices.
I hope this post motivated you to give it a try. Deploy your cluster on MongoDB Atlas and give the repository a spin.
For further guidance and support, and to engage with a vibrant community of developers, head over to the MongoDB Forum where you can find help, share insights, and ask those burning questions. Let's continue pushing the boundaries of Java development together!

Facebook Icontwitter iconlinkedin icon
Rate this article

Java - Aggregation Pipeline

Mar 01, 2024 | 8 min read

Migrating PostgreSQL to MongoDB Using Confluent Kafka

Apr 10, 2024 | 10 min read

Creating a REST API for CRUD Operations With Quarkus and MongoDB

Apr 17, 2024 | 7 min read

Java - MongoDB Multi-Document ACID Transactions

Mar 01, 2024 | 10 min read
Table of Contents