[Translation] SpringBoot Virtual Threads VS WebFlux: Performance Comparison for JWT Verification and MySQL Query
Summary
In benchmarks, Spring WebFlux is 57% faster than Spring Boot with virtual threads when verifying JWT tokens in a MySQL database. It seems we don't get any benefits from using virtual threads...
Introduction
After extensive evaluation of performance across a range of technologies (including Node.js, Deno, Bun, Rust, Go, Spring, Python, etc.) in simple "hello world" scenarios, I've gradually received a lot of feedback. Although these articles received considerable approval, readers often reflected that they didn't directly address real business use cases. Readers urged me to extend the analysis to more practical scenarios. Surprisingly, these articles still attracted a large readership. Nevertheless, this feedback does have merit. As a starting point, "hello world" is ideal, but it's far from representing real-world complexity.
Real-World Use Case
This article is part of our ongoing series aimed at dissecting various technologies through real-world scenarios. In this specific case, we'll dive deep into the following common use case:
- Extract JWT from the authorization header.
- Verify the JWT and extract the user's email from its claims.
- Execute a MySQL query using the extracted email.
- Finally return the user's record.
Although this scenario seems simple, it represents a practical challenge frequently encountered in web development.
Technical Section
In this article, we'll deeply compare SpringBoot (with virtual threads) and WebFlux, these sibling technologies, focusing on their performance in specific use case scenarios. We've already explored the performance comparison between standard SpringBoot applications and Webflux, and now we'll introduce a key differentiator:
SpringBoot (with Virtual Threads)
We have SpringBoot, but with a twist—it runs on virtual threads instead of traditional physical threads. Virtual threads are game changers in the concurrency domain. These lightweight threads simplify the complex task of developing, maintaining, and debugging high-throughput concurrent applications. While virtual threads still run on underlying OS threads, they bring significant efficiency improvements. When a virtual thread encounters a blocking I/O operation, the Java runtime temporarily suspends it, freeing the associated OS thread to serve other virtual threads. This elegant solution optimizes resource allocation and enhances overall application responsiveness.
SpringBoot Webflux
On the other side is Spring Boot Webflux. Spring Boot WebFlux is a reactive programming framework in the Spring ecosystem designed for building highly scalable asynchronous web applications. It leverages the Project Reactor library for non-blocking, event-driven programming. Spring Boot WebFlux is particularly suitable for applications requiring high concurrency and low latency, making it an excellent choice for building reactive microservices and real-time data-intensive applications. Based on its reactive nature, developers can efficiently handle large numbers of concurrent requests while flexibly integrating various data sources and communication protocols.
With these interesting features in mind, let's dive deeper into our performance comparison.
Test Environment and Software Versions
Our performance tests were conducted on a MacBook Pro M1 with 16GB RAM to ensure a reliable testing platform. The software used in testing includes:
- SpringBoot 3.1.3 (Running on Java 20)
- Virtual threads in preview mode
SpringBoot and Libraries Used
Our setup includes the following key components:
- SpringBoot 3.1.3 + Java 20 (virtual threads in preview mode)
- jjwt for JWT verification and decoding, enhancing security validation for our application
- mysql-connector-java for executing MySQL queries, maintaining data integrity and consistency
Load Testing and JWT
To evaluate our application's performance under different loads, we used the open-source load testing tool Bombardier. Our test scenario includes a pre-created list of 100,000 JWTs. During testing, Bombardier randomly selects JWTs from this pool and includes them in the HTTP request's authorization header.
MySQL Database Structure
The MySQL database used for these performance tests contains a table named users. This table is designed with 6 columns, sufficient to simulate real-world data interactions in our application, allowing us to evaluate their responsiveness and scalability.
mysql> desc users;
+--------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| email | varchar(255) | NO | PRI | NULL | |
| first | varchar(255) | YES | | NULL | |
| last | varchar(255) | YES | | NULL | |
| city | varchar(255) | YES | | NULL | |
| county | varchar(255) | YES | | NULL | |
| age | int | YES | | NULL | |
+--------+--------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
The initial dataset for the users database contains a substantial 100,000 user records.
mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
| 99999 |
+----------+
1 row in set (0.01 sec)
In our performance evaluation of SpringBoot virtual threads and Webflux, it's necessary to understand an important data relationship. Specifically, in the JSON Web Token (JWT) payload, each email record directly corresponds to a user record stored in the MySQL database.
Code Section
SpringBoot (virtual threads)
Configuration and business code are provided in the original article with User entity, UserController, and UserRepository implementations.
SpringBoot Webflux
Configuration and business code are provided with reactive implementations using R2DBC for non-blocking database access.
Results
To evaluate performance, we conducted a series of rigorous tests. Each test contained 5 million requests, and we evaluated performance at different concurrent connection levels: 50, 100, and 300.
Now, let's look at the results in a concise table format:



Summary
The scorecard is generated from the results using the following formula. For each measurement, the winning margin is calculated. If the winning margin is:
- Less than 5%, no points awarded
- Between 5% and 20%, winner gets 1 point
- Between 20% and 50%, winner gets 2 points
- 50% or more, winner gets 3 points


Even in practical cases like this, using virtual threads doesn't seem to have any advantage over webflux. If you're willing to share, please add your professional opinion on virtual threads in the comments.