DEV Community

Sadiul Hakim
Sadiul Hakim

Posted on

Java Virtual Threads Tutorial

Java Virtual Threads, introduced in JEP 425, are part of Project Loom and bring lightweight concurrency to the Java platform. They aim to make concurrent programming simpler and more scalable.

What is a Virtual Thread?

A Virtual Thread is a lightweight thread managed by the JVM, not by the operating system. Unlike traditional platform threads, virtual threads are cheap to create and allow high concurrency without the typical resource constraints.

  • Platform Thread: OS-managed, expensive in memory and context switching.
  • Virtual Thread: JVM-managed, cheap, can scale to millions of concurrent threads.
Thread virtualThread = Thread.ofVirtual().start(() -> {
    System.out.println("Running in a virtual thread");
});
Enter fullscreen mode Exit fullscreen mode

How Does it Work?

Virtual threads rely on the JVM's ability to suspend and resume threads efficiently. When a virtual thread performs a blocking operation (like I/O), the JVM can park it and reuse the underlying carrier thread for other tasks.

Key Concepts:

  • Carrier Thread: OS thread that executes virtual threads.
  • Park/Unpark: Mechanism to suspend and resume threads without consuming OS resources.
  • Continuation: Internal JVM feature that allows resuming execution where a thread left off.

Effectively, virtual threads decouple task execution from OS thread availability, allowing massive concurrency without overloading the system.

Benefits Over Platform Threads

Feature Platform Thread Virtual Thread
Cost High (memory & CPU) Very low
Scalability Thousands max Millions possible
Blocking I/O Blocks OS thread Does not block carrier thread
Scheduling OS scheduler JVM scheduler
Creation Expensive Cheap (microseconds)

Example: Creating 1 million platform threads is practically impossible; virtual threads handle it easily.

Problems Virtual Threads Solve

  1. Scalability Limits of platform threads in highly concurrent applications.
  2. Complexity of async/reactive code: Eliminates the need for callbacks and reactive frameworks in many cases.
  3. Resource inefficiency: Platform threads consume significant memory; virtual threads reduce footprint.

Can We Use Virtual Threads Instead of Reactive Programming?

Yes, in many cases:

  • Virtual threads make blocking I/O cheap, so you can write imperative code without losing scalability.
  • Reactive programming (e.g., Reactor, RxJava) was primarily a solution to non-blocking I/O. Virtual threads allow similar performance with synchronous code.
  • Caveat: For extremely high-throughput systems, reactive programming might still offer finer-grained control.

Different Ways of Creating Virtual Threads

Using Thread API

Thread vt = Thread.ofVirtual().start(() -> System.out.println("Virtual thread running"));
vt.join();
Enter fullscreen mode Exit fullscreen mode

Using ExecutorService

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

executor.submit(() -> {
    System.out.println("Task running in virtual thread");
});

executor.shutdown();
Enter fullscreen mode Exit fullscreen mode

Structured Concurrency (Java 21+)

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    scope.fork(() -> task1());
    scope.fork(() -> task2());
    scope.join(); // Wait for all
}
Enter fullscreen mode Exit fullscreen mode

Using Virtual Threads with Spring Boot

Spring Boot can leverage virtual threads in controller or service layers.

Spring Boot Property for Virtual Threads

# application.yml
spring:
  task:
    execution:
      type: virtual
      pool:
        keep-alive: 60s      # Optional, keep-alive time for idle threads
        max-size: 1000       # Optional, maximum threads (mostly advisory for virtual threads)
Enter fullscreen mode Exit fullscreen mode

Or in application.properties:

spring.task.execution.type=virtual
spring.task.execution.pool.keep-alive=60s
spring.task.execution.pool.max-size=1000
Enter fullscreen mode Exit fullscreen mode

How It Works

  • spring.task.execution.type=virtual tells Spring to use a virtual thread executor instead of the default ThreadPoolTaskExecutor.
  • You can still configure pool properties, but virtual threads are very lightweight, so the limits are mostly advisory.
  • All @Async tasks, scheduled tasks, or any Spring components using TaskExecutor will now run on virtual threads automatically.

Example: Async Service with Property-based Virtual Threads

Spring Boot exposes a property to use virtual threads for the TaskExecutor (used by @Async, scheduling, etc.).

@Service
public class AsyncService {

    @Async
    public void processTask(String name) {
        System.out.println(Thread.currentThread() + " processing " + name);
        try {
            Thread.sleep(2000); // blocking call is fine
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Controller:

@RestController
@RequiredArgsConstructor
public class TestController {

    private final AsyncService service;

    @GetMapping("/run")
    public String run() {
        for (int i = 0; i < 10; i++) {
            service.processTask("task-" + i);
        }
        return "Tasks submitted";
    }
}
Enter fullscreen mode Exit fullscreen mode

Now all tasks will run on virtual threads without manually configuring an executor.

With @Async and Executor

@Configuration
public class VirtualThreadConfig {

    @Bean
    public Executor virtualThreadExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits in Spring Boot

  • Simplifies blocking I/O handling (e.g., JDBC, REST calls).
  • Can scale endpoints handling thousands of concurrent requests.
  • Reduces need for reactive frameworks like WebFlux if your main bottleneck is I/O.

Best Practices

  1. Use structured concurrency for better lifecycle management.
  2. Use virtual threads for I/O-bound tasks; platform threads still good for CPU-heavy tasks.
  3. Combine with Spring Boot async features for scalable web apps.

Summary

  • Virtual threads bring lightweight, highly scalable concurrency.
  • They decouple task execution from OS threads, making blocking code cheap.
  • They can often replace reactive programming in I/O-heavy applications.
  • Integrates well with Spring Boot, allowing developers to write simple imperative code that scales.

Top comments (0)