Java / Java 21 Virtual Threads Interview questions
1. What is a virtual thread in Java 21?
A virtual thread is a lightweight thread implementation introduced as a stable feature in Java 21 under JEP 444. Unlike a regular thread, it isn't tied one-to-one to an operating system thread. The JVM itself manages virtual threads and multiplexes many of them onto a small pool of real OS thread...
2. What is Project Loom?
Project Loom is the OpenJDK project that redesigned Java's concurrency model to make writing highly concurrent applications simpler without abandoning the familiar thread-per-request coding style. Its output shipped across several JEPs: virtual threads (JEP 444, final in Java 21), structured conc...
3. What are the types of threads available in Java 21?
Java 21 supports two kinds of java.lang.Thread : platform threads and virtual threads . Both share the same public API, but they behave very differently underneath. Platform Thread Virtual Thread Maps 1:1 to an OS thread. Many virtual threads share a small pool of OS carrier threads. Scheduled by...
4. How do you create a virtual thread?
Java 21 gives you a few equivalent entry points, all producing an actual java.lang.Thread whose isVirtual() returns true . // Quick one-off Thread t = Thread.startVirtualThread(() -> System.out.println("running")); // Builder style, more control Thread t2 = Thread.ofVirtual() .name("worker-1") .s...
5. What is the purpose of virtual threads?
Virtual threads exist to raise the throughput of concurrent applications that spend most of their time waiting on I/O, such as network calls or database queries, without forcing developers into reactive or callback-based code. Before Java 21, scaling I/O-bound concurrency usually meant either exh...
6. What is a platform thread?
A platform thread is the traditional Java thread that existed before virtual threads. Each platform thread wraps exactly one operating system thread in a 1:1 relationship. It's scheduled by the OS, carries a relatively large default stack (around 1MB, though this is platform-dependent), and creat...
7. Define a carrier thread?
A carrier thread is a platform thread that the JVM scheduler uses to actually execute a virtual thread's code. It "carries" the virtual thread while it is mounted . Carrier threads come from a dedicated internal pool, sized by default to the number of available processor cores, and are shared acr...
8. Describe the Thread.Builder API?
The Thread.Builder API is a fluent way to configure and create threads, introduced alongside virtual threads. It comes in two flavors: Thread.ofPlatform() and Thread.ofVirtual() , both returning a builder. Thread t = Thread.ofVirtual() .name("order-processor-", 0) .uncaughtExceptionHandler((th, e...
9. What is the purpose of Thread.ofVirtual()?
Thread.ofVirtual() is the entry point for creating and configuring virtual threads. It returns a Thread.Builder.OfVirtual instance rather than a thread itself. From that builder you can chain a name pattern, an uncaught exception handler, and finally call .start(runnable) to launch the thread or ...
10. How do you check if a thread is virtual?
Java 21 added an instance method, isVirtual() , directly on Thread , so you can check any thread reference at runtime. Thread current = Thread.currentThread(); if (current.isVirtual()) { System.out.println( "Running on a virtual thread" ); } else { System.out.println( "Running on a platform threa...
11. What is a continuation in the context of virtual threads?
A continuation is a low-level JVM construct that captures a computation's execution state so it can be paused and later resumed from exactly where it left off, including its call stack. Virtual threads are built on top of an internal continuation mechanism: when a virtual thread blocks, the JVM f...
12. List the ways to create virtual threads in Java 21?
There are four common entry points for getting a virtual thread running, all producing the same underlying kind of thread: Thread.startVirtualThread(Runnable) - creates and starts one immediately. Thread.ofVirtual().start(Runnable) - builder style, with optional naming or exception handling. Thre...
13. What is the default naming behavior for virtual threads?
Unlike platform threads, which the JVM auto-names something like Thread-0 , Thread-1 , and so on, virtual threads are unnamed by default - their name is an empty string. This matters because with potentially millions of short-lived virtual threads, generic incrementing names would add little debu...
14. How do you use Executors.newVirtualThreadPerTaskExecutor()?
This factory method returns an ExecutorService that starts a brand-new virtual thread for every task you submit - there's no pooling and no queue limit, since virtual threads are cheap enough to create on demand. try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { for (...
15. What is structured concurrency?
Structured concurrency is a programming model where a group of related concurrent subtasks is treated as a single unit of work that shares a lifetime, entry point, and exit point with its parent task. In Java 21 it's delivered through StructuredTaskScope as a preview API (JEP 453, requiring --ena...
16. What is the difference between virtual threads and platform threads?
The core difference is who does the scheduling and how expensive creation is . Platform threads map 1:1 to OS threads and are scheduled by the operating system; virtual threads are scheduled by the JVM and share a small pool of carrier threads. Aspect Platform Thread Virtual Thread Mapping to OS ...
17. Why is thread pinning a concern with virtual threads?
Pinning happens when a virtual thread cannot be unmounted from its carrier while it's blocked, forcing the carrier thread to sit idle instead of running other virtual threads. This defeats the scalability benefit virtual threads are meant to provide. The two main triggers in Java 21 are blocking ...
18. Why do we use virtual threads instead of traditional thread pools?
A traditional bounded thread pool caps concurrency at a fixed number of OS threads. Under heavy I/O-bound load, once every pooled thread is blocked waiting on something slow, new work simply queues up, adding latency even though the CPU is mostly idle. Virtual threads remove that ceiling: because...
19. How does the JVM schedule virtual threads?
By default, virtual threads are scheduled by a dedicated ForkJoinPool instance running in FIFO mode , separate from the common pool used by parallel streams. Each worker in that pool acts as a carrier thread. When a virtual thread becomes runnable (freshly started, or resumed after a blocking ope...
20. How is a virtual thread mounted onto a carrier thread?
Mounting is the act of a carrier thread picking up a virtual thread's continuation and executing its code. Unmounting is the reverse: detaching that continuation so the carrier is free for other work. When the scheduler assigns a runnable virtual thread to an idle carrier, the JVM restores (thaws...
21. When should you use virtual threads over platform threads?
Reach for virtual threads when your workload is dominated by waiting rather than computing: handling many simultaneous HTTP requests, calling downstream services, querying databases, or performing file I/O. They shine in a thread-per-request server design, where each incoming request gets its own...
22. When would you choose platform threads over virtual threads?
Platform threads still make sense for CPU-bound work, where the bottleneck is computation rather than waiting - running more threads than you have cores won't speed that up regardless of thread type. They're also preferable for code that unavoidably relies on heavy synchronized blocks combined wi...
23. What happens when a virtual thread performs blocking I/O?
Most blocking APIs in the JDK - socket reads, java.net.http calls, file operations - have been reworked internally so that, when invoked from a virtual thread, they don't actually block the underlying carrier thread. Instead, the JVM registers interest in the I/O event (often via an NIO selector ...
24. Why doesn't ThreadLocal work well with virtual threads?
ThreadLocal was designed under the assumption that threads are relatively few and long-lived, often reused via a pool, so caching a value per thread was cheap and effective. With virtual threads, you may have millions of short-lived instances, each potentially allocating its own ThreadLocal entry...
25. What is a ScopedValue and why was it introduced?
A ScopedValue (JEP 446, preview in Java 21) is an immutable value that's bound only for the duration of a specific call and automatically visible to any child threads it spawns, without needing manual propagation. static final ScopedValue
26. How does StructuredTaskScope manage a group of virtual threads?
StructuredTaskScope lets you fork several subtasks, each running on its own virtual thread, and treat them as one unit bound to the enclosing block's lifetime. try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Subtask
27. Why should you avoid pooling virtual threads?
Pooling exists to amortize an expensive resource - creating a platform thread costs a native stack allocation and OS bookkeeping, so reusing a small set of them made sense. Virtual threads flip that assumption: creating one costs little more than allocating a small object on the heap. Pooling the...
28. What is the difference between synchronized blocks and ReentrantLock for pinning?
In Java 21, when a virtual thread blocks while holding a monitor acquired through a synchronized block or method, it stays pinned to its carrier thread - the JVM can't unmount it mid-monitor-hold, so the carrier is stuck idle for that duration. // Pins the carrier if lock1.lock() blocks inside sy...
29. How does virtual thread stack size differ from a platform thread's?
A platform thread reserves a fixed native stack up front, commonly around 1MB depending on the OS and JVM flags, whether or not the thread ever needs that much space. A virtual thread's stack instead starts very small and lives on the heap as part of its continuation object, growing and shrinking...
30. What happens when you call Thread.sleep() inside a virtual thread?
Thread.sleep() has been adapted so that, when called from a virtual thread, it doesn't tie up the carrier thread for the sleep duration. Thread.startVirtualThread(() -> { Thread.sleep(Duration.ofSeconds( 5 )); // carrier is freed during this wait System.out.println( "woke up" ); }); Instead, the ...
31. Why is the number of carrier threads limited by default?
By default, the virtual thread scheduler's parallelism equals Runtime.availableProcessors() - the number of CPU cores visible to the JVM. Carrier threads are the ones that actually execute CPU instructions on behalf of virtual threads, so running more of them than you have cores doesn't add real ...
32. What is the difference between ExecutorService and StructuredTaskScope?
ExecutorService is a general-purpose task submission abstraction: tasks you submit can outlive the method that submitted them, and you're responsible for manually calling shutdown() and awaiting termination. StructuredTaskScope is narrower and more disciplined by design - subtasks forked inside i...
33. How does virtual thread creation cost compare to platform threads?
Creating a platform thread involves the OS allocating and page-mapping a full native stack (often ~1MB) plus kernel-level bookkeeping, which typically costs on the order of tens of microseconds to low milliseconds and real memory. Creating a virtual thread instead allocates a small continuation o...
34. Why do virtual thread priorities have little practical effect?
Calling setPriority() on a virtual thread is effectively a no-op, and getPriority() always reports Thread.NORM_PRIORITY , regardless of what you set. Thread priority historically was a hint passed down to the OS scheduler, which decides how to time-slice platform threads. Virtual threads aren't s...
35. How do you configure the number of carrier threads used?
Two system properties control the virtual thread scheduler's carrier pool: jdk.virtualThreadScheduler.parallelism sets the baseline pool size (default is the core count), and jdk.virtualThreadScheduler.maxPoolSize sets an upper bound the JVM can grow to temporarily. java -Djdk.virtualThreadSchedu...
36. Explain the lifecycle of a virtual thread?
A virtual thread moves through the same Thread.State enum as a platform thread, but the transitions are driven by the JVM scheduler rather than the OS. stateDiagram-v2 [*] --> NEW NEW --> RUNNABLE : start() RUNNABLE --> RUNNING : mounted on a carrier RUNNING --> WAITING : blocking call, park(), o...
37. Explain the execution flow when a virtual thread makes a blocking call?
Consider a virtual thread issuing a network read. The sequence below shows how the JVM keeps the carrier thread productive instead of letting it sit idle. sequenceDiagram participant VT as Virtual Thread participant Sched as Scheduler participant Carrier as Carrier Thread participant IO as OS / N...
38. Explain the internal working of the scheduler behind virtual threads?
The default scheduler is a dedicated instance of java.util.concurrent.ForkJoinPool running in FIFO mode (as opposed to the LIFO/work-stealing mode used by the common pool for parallel streams), separate from that common pool entirely. Each worker thread in this pool is a carrier thread. Runnable ...
39. How can you optimize an application to fully benefit from virtual threads?
Getting the full benefit of virtual threads takes more than swapping in a new executor - a handful of practices matter most: Never pool virtual threads - create one per task instead of reusing a fixed set. Replace synchronized with ReentrantLock around any code path that also performs a blocking ...
40. How do you troubleshoot thread pinning caused by synchronized blocks?
Start by making pinning visible . The JVM can print a stack trace every time a virtual thread pins its carrier by launching with a diagnostic flag: java -Djdk.tracePinnedThreads=full -jar myapp.jar Use short instead of full for a more compact trace. You can also capture the jdk.VirtualThreadPinne...
41. How do you migrate a thread-pool-based application to virtual threads?
Migration works best as a staged process rather than a blanket swap: Replace the outermost, request-facing ExecutorService (e.g. a fixed thread pool) with Executors.newVirtualThreadPerTaskExecutor() , since that's where the I/O-bound, high-fan-out benefit is greatest. Audit code for pinning-prone...
42. How do you monitor and debug virtual threads using JFR?
Java Flight Recorder gained several events specific to virtual threads: jdk.VirtualThreadStart and jdk.VirtualThreadEnd for lifecycle tracking, jdk.VirtualThreadPinned for pinning incidents, and jdk.VirtualThreadSubmitFailed for scheduler-level failures. Recording these over time lets you correla...
43. How can you detect thread pinning in a production system?
In production you generally want continuous, low-overhead visibility rather than a one-off diagnostic run, so a persistent JFR recording subscribed to the jdk.VirtualThreadPinned event is the usual approach. Feed that event stream into your existing observability pipeline and correlate spikes in ...
44. Which is better and why: thread pools or virtual threads for I/O-heavy services?
For most I/O-heavy services, virtual threads are the better fit: each request can hold its own thread through the entire blocking call chain without consuming a scarce OS thread, removing the pool-exhaustion queueing that limits a fixed-size platform thread pool and letting you keep straightforwa...
45. Explain how virtual threads interact with native code and JNI calls?
When a virtual thread enters native code through JNI, or performs an operation the JVM hasn't made "Loom-aware," it pins to its current carrier thread for the entire duration of that native call. This happens because the JVM has no general mechanism to suspend a native call mid-execution and resu...
46. What is the difference between virtual threads and reactive programming?
Reactive frameworks like Project Reactor or RxJava achieve high concurrency by never blocking at all - I/O completion triggers callbacks composed through operator chains ( map , flatMap , and so on), which avoids tying up any thread while waiting. The trade-off is a different programming model: s...
47. What is the difference between virtual threads and Kotlin coroutines or Go goroutines?
All three are lightweight, cooperatively scheduled units of concurrency, far cheaper than OS threads, but they sit at different layers of their respective platforms. Kotlin coroutines are a language and library-level feature: functions marked suspend are transformed by the compiler into continuat...
48. Explain the internal working of StructuredTaskScope's shutdown behavior?
Policies like ShutdownOnFailure and ShutdownOnSuccess watch the subtasks forked within a scope for a triggering condition - the first failure, or the first success, respectively. Once that condition fires, the scope calls shutdown() internally, which interrupts every remaining forked virtual thre...
49. How does the JVM avoid exhausting memory with millions of virtual threads?
The key is where and how a thread's stack is stored. A platform thread reserves a fixed native stack - commonly around 1MB - at creation time, whether it's needed or not, and that memory sits outside the managed Java heap. A virtual thread's stack instead starts tiny and lives inside its continua...
50. Explain the execution flow of a structured concurrency task with subtasks failing?
Consider forking two subtasks under StructuredTaskScope.ShutdownOnFailure , where the first one throws an exception partway through. Both subtasks are forked as virtual threads and begin running concurrently. The first subtask throws an exception; the scope's failure policy captures it immediatel...