Prev Next

Java / Java 21 Virtual Threads Interview questions

Last updated

Last updated 19 September 2026. Default examples are mid-level Spring and Java 17–21. Junior and senior sit in labeled sections so the first screen is not a fresher dump.

Java 21 is the LTS most new services should target in 2026. Virtual threads are the headline. Sequenced collections, pattern matching for switch, and record patterns show up in the same interview. This hub is the Java 21 interview page. The collections hub covers sequenced types in more depth; this page covers the platform change.

Junior

Virtual threads are JVM-scheduled threads that do not consume a platform thread while they block on most Java I/O. You create them with Thread.ofVirtual().start or Executors.newVirtualThreadPerTaskExecutor(). They are Thread instances. Thread.currentThread() still works. Do not pool virtual threads the way you pooled platform threads; the point is one task per virtual thread.

A platform thread is an OS thread. A virtual thread mounts on a carrier (a platform thread from a ForkJoinPool). When it blocks in a virtual-thread-friendly point, it unmounts and the carrier runs someone else. That is the model. Junior candidates who say 'virtual threads are faster CPU' fail the interview.

Pattern matching for switch and record patterns reduce casts. instanceof with binding is already on 16+. Java 21 makes more of this final. SequencedCollection adds getFirst and reversed. Know the API exists.

StringTemplates were a preview and then withdrawn; do not memorize a dead preview as if it shipped forever. Date your answer.

Mid-level

Pinning: a virtual thread that enters a synchronized block or native call may pin its carrier. A pinned carrier cannot run other virtual threads. Lots of pinning plus a small carrier pool looks like a deadlock. jcmd Thread.dump_to_file and JFR pin events are how you see it. ReentrantLock is often friendlier than synchronized for this generation of code, but measure; the story is still moving.

Structured concurrency (preview in 21, evolving later) groups tasks under a scope. If one fails, the others cancel. This is the API you want instead of a bag of CompletableFutures you forget to join.

Executors.newVirtualThreadPerTaskExecutor() plus blocking JDBC is the mid-level design for high-concurrency I/O. CPU-bound work still wants a bounded platform pool. Mixing both is normal. Tomcat and Boot can run requests on virtual threads; that does not make Hibernate session-per-request free if you pin.

Compatibility: bytecode from older JDKs runs. Some libraries still assume one-thread-per-request ThreadLocals for security context. Propagate context explicitly. Review synchronized blocks in dependencies if you go wide on virtual threads.

Senior

Senior Java 21 is continuation implementation, carrier pool size, and how monitor deflation and pinning interact with your hottest lock. Do not enable virtual threads globally on a service with a huge synchronized cache without a pin profile.

GC and virtual threads: many threads no longer mean many stacks in native memory the old way. Heap objects for stacks still exist. Tune as usual; do not assume RSS drops.

FFM (Foreign Function and Memory) is the replacement path for some JNI. It is a separate interview from virtual threads. Mention it if they ask about Java 21 features beyond Loom.

Migration: 8 to 17 was the painful one (javax to jakarta if you also jumped Boot 3). 17 to 21 is smaller. Removed APIs and finalization deprecation matter more than syntax. Run jdeps and the compiler on a real module path before you promise a date.

Probe yourself

Should you pool virtual threads?

No. Create one per task. Pool platform threads for CPU-bound work.

What is pinning?

The virtual thread stays mounted on a carrier, usually in synchronized or native code, so the carrier cannot run other virtual threads.

Are virtual threads faster at number crunching?

No. They help concurrency when tasks block. CPU-bound work still needs a bounded platform pool.

Related questions on this topic are linked below. Read the full answer on the question URL; this hub does not repeat those answers.

Pitfalls interviewers still use

newVirtualThreadPerTaskExecutor() around a CPU-bound hash or a tight crypto loop. You will create a million tasks that all want carriers. Use a bounded platform pool for CPU. Use virtual threads for blocking I/O and high fan-out.

Assuming synchronized is fine because 'it works on my laptop'. Under load, a hot synchronized cache pins carriers. Profile pins before you call the migration done.

Thread.sleep in a test as a substitute for a lock or a latch. It will pass on a quiet CI agent and fail on a busy one. Use a latch or Awaitility with a condition.

Copying ThreadLocal from a tutorial into a virtual-thread world without checking whether the library uses InheritableThreadLocal or a scope-local (later Java). Be honest about the version you shipped.

Saying structured concurrency is 'final in 21'. It was preview. Date the JEP. Interviewers use this to see if you read release notes or Twitter.

For the room: explain mount, unmount, pin, and one library you would audit before flipping Tomcat to virtual threads. That is a 2026 answer.

How to answer in the room

Define a virtual thread as a JVM-scheduled thread that mounts on a carrier (a platform thread) to run, then unmounts on blocking points the runtime understands. It is not a goroutine you can pretend has no cost. Heap usage and scheduler fairness still exist.

Use them for many concurrent blocking operations: HTTP calls, JDBC that unmounts, fan-out to many services. Do not use a virtual-thread-per-task executor as a CPU thread pool for hashing or tight loops. Those tasks want a bounded platform pool.

Pinning: synchronized blocks and some native calls keep the carrier. A hot synchronized cache can stall the scheduler. ReentrantLock often unmounts better in current JDKs, but you measure pins; you do not recite a tweet. Thread dumps look different; learn the new frames.

Tomcat or Jetty can run request handling on virtual threads in current Boot. That does not make a pool of 10 database connections into 10,000. Hikari still sizes to the database. You will queue on the pool, not on the JVM, if you ignore this.

Structured concurrency in 21 is preview. StructuredTaskScope is the API to name. Do not say it is final. Scoped values replace some ThreadLocal stories in later versions; date them. If the job is on 21 only, do not invent 25 APIs.

Collections on 21: sequenced collections are the other interview. getFirst, getLast, reversed. Mention them on this hub when the query is 'Java 21 interview', then point to the collections hub for structure choice. Do not write a second 50-question Java 21 dump.

Migration story: start with a blocking I/O service, audit synchronized and ThreadLocal, watch pins and pool queues, then flip the executor. A big-bang rewrite of CPU services onto virtual threads is how you create a carrier starvation incident.

If they ask whether you would enable virtual threads on this Heroku dyno tomorrow, say no until you know the JDBC driver, the lock profile, and the pool size. A 256m heap plus a million tasks is a different outage than a pin. Answer with the constraint you have, not with a conference slide. A yes without those three facts is a guess, and guesses ship incidents.

Interview questions

1. 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...

Read full answer

2. 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...

Read full answer

3. 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.

Read full answer

4. 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...

Read full answer

5. 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...

Read full answer

6. 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...

Read full answer

7. 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.

Read full answer

8. 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....

Read full answer

Next in this series: Spring Data Access
«
»

Comments & Discussions