Spring Boot: Evaluating the Speed of Virtual Threads in Database Read Operations

Naveen Metta
4 min readJun 3, 2024

--

credit goes to the owner : https://jenkov.com/tutorials/java-concurrency/java-virtual-threads.html
source : jenkov.com

Virtual threads are the new hot topic in Java, especially when it comes to improving concurrency. With the advent of Project Loom, Java introduced virtual threads that promise to revolutionize the way we handle threads. In this article, we’ll dive deep into how fast these virtual threads are, specifically in the context of database read operations using Spring Boot. We’ll back up our discussion with ample Java code examples to give you a clear picture.

Introduction

Spring Boot has long been a favorite for creating robust and scalable Java applications. However, threading and concurrency have always been tricky areas, especially with the traditional heavyweight threads. Enter virtual threads, lightweight counterparts that can potentially handle thousands of concurrent operations with ease. But how do they fare in a real-world scenario like reading from a database?

What Are Virtual Threads?

Before we delve into the performance metrics, let’s understand what virtual threads are. Virtual threads are a part of Project Loom and are designed to be lightweight and efficient, unlike traditional platform threads. They provide a simpler and more scalable concurrency model by decoupling the cost of thread creation from the number of concurrent tasks.

Setting Up Spring Boot with Virtual Threads

To test the performance, we need a Spring Boot application configured to use virtual threads. Here’s a step-by-step guide to set it up:

  1. Create a Spring Boot Project: Start by generating a new Spring Boot project. You can use Spring Initializr or your favorite IDE to set it up.
  2. Add Dependencies: Ensure you have the necessary dependencies in your pom.xml or build.gradle file. You’ll need Spring Data JPA and your preferred database driver (e.g., H2 for simplicity).
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
  1. Enable Virtual Threads: Use the JVM option -Djava.util.concurrent.ForkJoinPool.common.parallelism to enable virtual threads.

Implementing Database Read Operations

Now, let’s implement the code to perform database read operations using virtual threads.

Entity and Repository Setup

First, define a simple entity and repository.

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;

// Getters and Setters
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

Service Layer

Next, create a service to handle database read operations.

@Service
public class UserService {

@Autowired
private UserRepository userRepository;

public List<User> getAllUsers() {
return userRepository.findAll();
}

public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
}

Controller Layer

Then, set up a controller to expose these services via REST endpoints.

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService userService;

@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}

@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
}

Virtual Threads Execution

Now, let’s modify our service to use virtual threads for database read operations.

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

@Service
public class UserService {

@Autowired
private UserRepository userRepository;

private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

public List<User> getAllUsers() {
try {
return executor.submit(() -> userRepository.findAll()).get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public User getUserById(Long id) {
try {
return executor.submit(() -> userRepository.findById(id).orElse(null)).get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

Performance Testing

To evaluate performance, we need to compare the execution time of virtual threads versus traditional threads.

Traditional Threads

Modify the UserService to use a fixed thread pool executor for comparison.

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

@Service
public class UserService {

@Autowired
private UserRepository userRepository;

private final ExecutorService executor = Executors.newFixedThreadPool(10);

public List<User> getAllUsers() {
try {
return executor.submit(() -> userRepository.findAll()).get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public User getUserById(Long id) {
try {
return executor.submit(() -> userRepository.findById(id).orElse(null)).get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

Benchmarking

Create a simple benchmarking tool to measure execution time.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class BenchmarkRunner implements CommandLineRunner {

@Autowired
private UserService userService;

@Override
public void run(String... args) throws Exception {
long start = System.currentTimeMillis();
userService.getAllUsers();
long end = System.currentTimeMillis();
System.out.println("Execution time: " + (end - start) + " ms");
}
}

Results and Analysis

After running both configurations (virtual threads and traditional threads), you’ll likely notice a significant difference in execution times. Virtual threads, being lightweight, can handle many more concurrent operations compared to traditional threads. This makes them especially advantageous for applications with high concurrency requirements, like web applications performing numerous database reads.

Conclusion

Virtual threads in Java, introduced through Project Loom, provide a scalable and efficient way to handle concurrency. When integrated with Spring Boot, they can dramatically improve the performance of database read operations. By shifting from traditional heavyweight threads to virtual threads, you can handle more concurrent tasks with less overhead, resulting in faster and more efficient applications.

--

--

Naveen Metta

I'm a Full Stack Developer with 2.5 years of experience. feel free to reach out for any help : mettanaveen701@gmail.com