Monday, 13 July 2026

Managing Virtual Threads with ExecutorService in Java

  

In Java, most developers don’t usually create threads by hand. Instead, they use something called an ExecutorService. Think of it as a manager that takes your tasks (pieces of work) and runs them using a pool of worker threads. Traditionally, these thread pools are limited in size because platform threads (the normal threads we’ve been using in Java for years) are expensive to create and manage.

 

But with virtual threads, things change. Virtual threads are so lightweight that we don’t need to worry about pool sizes. Java can create a separate virtual thread for each task, and the system can still run efficiently.

 

Example

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
	executor.submit(VirtualThreadsWithExecutorService::handleUserRequest);
	executor.submit(VirtualThreadsWithExecutorService::handleUserRequest);
}

   

Here:

·      We create an executor that assigns a new virtual thread for every task.

·      We submit two tasks (handleUserRequest)

·      The try-with-resources block automatically shuts down the executor after use. This means we don’t have to call shutdown() or join() manually, it waits until all tasks are done before closing.

 

Find the below working Application.

 

VirtualThreadsWithExecutorService.java

 

package com.sample.app.virtual.threads.creation;

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

public class VirtualThreadsWithExecutorService {

	static void handleUserRequest() {
		try {
			Thread.sleep(2000); // simulate some work
			System.out.println("Handled by: " + Thread.currentThread());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws Exception {
		// Create an ExecutorService that runs each task on its own virtual thread
		try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
			executor.submit(VirtualThreadsWithExecutorService::handleUserRequest);
			executor.submit(VirtualThreadsWithExecutorService::handleUserRequest);
		}

		// This line will only run after both tasks finish
		System.out.println("Main ends after all tasks are done");
	}

}

Output

Handled by: VirtualThread[#26]/runnable@ForkJoinPool-1-worker-2
Handled by: VirtualThread[#28]/runnable@ForkJoinPool-1-worker-1
Main ends after all tasks are done

   

In summary:

·      Executors manage tasks for you, you don’t have to manually start or join threads.

·      With virtual threads, you can have one thread per task without performance issues.

·      try-with-resources ensures the executor shuts down cleanly after all tasks are complete.

·      This approach is the best practice for task based programming with virtual threads.

·      You can easily combine this with features like Future or CompletableFuture to handle results.


Previous                                                    Next                                                    Home

No comments:

Post a Comment