Java 21 offers several ways to start virtual threads: Thread.ofVirtual(), Thread.startVirtualThread() and Executors.newVirtualThreadPerTaskExecutor(). In Spring Boot 3.2 and later, a single property switches the web server and async tasks to virtual threads.
Key Points: • Thread.startVirtualThread(runnable) starts one immediately. • Thread.ofVirtual().name("worker-", 0).start(runnable) uses a builder to set names and options. • Executors.newVirtualThreadPerTaskExecutor() creates a new virtual thread for each submitted task. It is the usual choice for existing ExecutorService code. • Spring Boot: add spring.threads.virtual.enabled=true to application.properties. Tomcat, @Async, @Scheduled and others then run on virtual threads. • Existing blocking code (JDBC, RestTemplate) benefits without rewriting it as reactive code.
Code Example:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CreateVirtualThreads {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 1. Start directly
Thread t1 = Thread.startVirtualThread(() -> System.out.println("Hello from a virtual thread"));
t1.join();
// 2. Builder with a name
Thread t2 = Thread.ofVirtual().name("order-worker-", 1)
.start(() -> System.out.println("Running in " + Thread.currentThread().getName()));
t2.join();
// 3. Executor: one virtual thread per task
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> price = executor.submit(() -> "Price: 499");
Future<String> stock = executor.submit(() -> "Stock: 12");
System.out.println(price.get() + ", " + stock.get());
}
System.out.println("t1 virtual? " + t1.isVirtual());
}
}Output:
Hello from a virtual thread Running in order-worker-1 Price: 499, Stock: 12 t1 virtual? true
Good to Know: For a Spring Boot role, mention the property spring.threads.virtual.enabled=true and say you would load test before and after, watching for pinning and for limits elsewhere (such as the database connection pool), which become the next bottleneck.
Interview Tip: A concise interview answer is:
"I create them with Thread.startVirtualThread, Thread.ofVirtual().start, or most often Executors.newVirtualThreadPerTaskExecutor, which gives each task its own virtual thread. In Spring Boot 3.2+, I enable them with spring.threads.virtual.enabled=true, so Tomcat and @Async use virtual threads without code changes."
Previous question: When should you not use virtual threads, and what is thread pinning?