Sunday, 12 July 2026

Create Virtual Threads Using a Virtual Thread Builder in Java

  

In Java, virtual threads (introduced in Project Loom) make it easier to write concurrent applications without worrying about the heavy cost of platform threads. The simplest way to create a virtual thread is with Thread.ofVirtual().start(...), but sometimes you need more control like giving threads meaningful names, or setting up exception handlers.

 

That’s where the Virtual Thread Builder comes in. It works like a factory that can create multiple threads with consistent settings.

 

VirtualThreadBuilder1.java

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

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

	public static void main(String[] args) throws InterruptedException {
		// Create a virtual thread builder with a custom name pattern
		Thread.Builder builder = Thread.ofVirtual().name("user-thread-", 0);

		// Start threads using the builder
		Thread t1 = builder.start(VirtualThreadBuilder1::handleUserRequest);
		Thread t2 = builder.start(VirtualThreadBuilder1::handleUserRequest);

		// Wait for both to finish
		t1.join();
		t2.join();
	}
}

   

Output

 

Handled by: VirtualThread[#26,user-thread-0]/runnable@ForkJoinPool-1-worker-1
Handled by: VirtualThread[#28,user-thread-1]/runnable@ForkJoinPool-1-worker-2

   

Thread.Builder builder = Thread.ofVirtual().name("user-thread-", 0);

We create a builder with a name pattern. This means new threads will automatically be named user-thread-0, user-thread-1, etc.

 

Thread t1 = builder.start(VirtualThreadBuilder1::handleUserRequest);

Thread t2 = builder.start(VirtualThreadBuilder1::handleUserRequest);

We use the builder to start multiple threads. Each thread runs the same method but gets its own name.

 

When you’re running thousands of virtual threads, debugging can become messy if all threads have default names like VirtualThread[#]. By naming them meaningfully, you can quickly spot which thread is handling what. Imagine profiling your application, you’ll immediately see user-thread-0 and user-thread-1 instead of just numbers. This helps a lot when analyzing logs or debugging.

 

Example 1: Adding an Uncaught Exception Handler

Thread.Builder builder = Thread.ofVirtual()
    .name("api-thread-", 0)
    .uncaughtExceptionHandler((t, e) -> {
        System.out.println("Thread " + t.getName() + " crashed: " + e.getMessage());
    });

   

Now, if any thread throws an uncaught exception, you’ll know exactly which one failed.

 

ThreadBuilderExceptionHadnler.java

 

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

public class ThreadBuilderExceptionHadnler {
    static void handleUserRequest() {
        int a = 10 /0 ;
        System.out.println(a);
    }

    public static void main(String[] args) throws InterruptedException {
        // Create a virtual thread builder with a custom name pattern
        Thread.Builder builder = Thread.ofVirtual()
                .name("api-thread-", 0)
                .uncaughtExceptionHandler((t, e) -> {
                    System.out.println("Thread " + t.getName() + " crashed: " + e.getMessage());
                });

        // Start threads using the builder
        Thread t1 = builder.start(ThreadBuilderExceptionHadnler::handleUserRequest);
        Thread t2 = builder.start(ThreadBuilderExceptionHadnler::handleUserRequest);

        // Wait for both to finish
        t1.join();
        t2.join();
    }
}

   

Output

 

Thread api-thread-0 crashed: / by zero
Thread api-thread-1 crashed: / by zero

   

Example 2: Creating Multiple Threads in a Loop

ThreadBuilderCreateMultipleThreads.java

 

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

import java.util.concurrent.TimeUnit;

public class ThreadBuilderCreateMultipleThreads {

    public static void main(String[] args) throws InterruptedException {
        Thread.Builder builder = Thread.ofVirtual().name("worker-", 0);

        for (int i = 0; i < 5; i++) {
            builder.start(() -> {
                System.out.println("Work done by: " + Thread.currentThread());
            });
        }
        
        TimeUnit.SECONDS.sleep(1);

    }

}

   

Output

 

Work done by: VirtualThread[#34,worker-3]/runnable@ForkJoinPool-1-worker-4
Work done by: VirtualThread[#31,worker-2]/runnable@ForkJoinPool-1-worker-3
Work done by: VirtualThread[#28,worker-1]/runnable@ForkJoinPool-1-worker-2
Work done by: VirtualThread[#36,worker-4]/runnable@ForkJoinPool-1-worker-6
Work done by: VirtualThread[#26,worker-0]/runnable@ForkJoinPool-1-worker-1

   

Points to consider

·      A Virtual Thread Builder is useful when you want multiple threads with the same configuration (naming pattern, exception handling, etc.).

·      It is not thread safe, so don’t share a single builder across multiple concurrent threads.

·      Makes debugging, logging, and profiling easier by giving threads consistent names.

·      Great for structured concurrency when handling multiple requests or tasks.

 

By using a Virtual Thread Builder, you gain flexibility and control without adding complexity. It’s a small step, but it makes your code cleaner, more maintainable, and much easier to debug in real-world applications.


  

Previous                                                    Next                                                    Home

No comments:

Post a Comment