Platform vs Virtual Threads
When each is appropriate.
Platform vs Virtual Threads is a free Java Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Two Kinds of Threads
Java now has two thread flavors. Platform threads are the classic wrappers around OS threads. Virtual threads are lightweight and JVM-managed.
Knowing when to reach for each is the heart of writing scalable concurrent Java.
Creating a Platform Thread
You create a platform thread with Thread.ofPlatform(), the mirror of Thread.ofVirtual().
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread p = Thread.ofPlatform().name("native-worker").start(() ->
System.out.println("Platform? " + !Thread.currentThread().isVirtual()));
p.join();
}
}Cost Comparison
Platform threads are heavy:
- ~1 MB reserved stack each
- Slow to create
- Limited to a few thousand
Virtual threads are cheap: small initial stack, fast creation, millions feasible.
I/O-Bound Work
If your task spends most of its time waiting on network, disk, or a database, virtual threads win big.
While one virtual thread blocks, its carrier is freed to run others, so a handful of OS threads serve thousands of requests.
CPU-Bound Work
For pure computation that never blocks, virtual threads offer no speedup. You are bounded by core count.
Here a fixed pool sized to the processors is the right tool.
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws Exception {
int cores = Runtime.getRuntime().availableProcessors();
try (ExecutorService cpu = Executors.newFixedThreadPool(cores)) {
Future<Long> f = cpu.submit(() -> {
long s = 0;
for (long i = 0; i < 1_000_000L; i++) s += i;
return s;
});
System.out.println("Cores: " + cores + ", sum: " + f.get());
}
}
}Simulating Blocking
This snippet shows many virtual threads sleeping (a stand-in for blocking I/O) concurrently. Try imagining 10,000 platform threads doing the same.
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) {
AtomicInteger done = new AtomicInteger();
try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
exec.submit(() -> {
try { Thread.sleep(50); } catch (InterruptedException e) {}
done.incrementAndGet();
});
}
}
System.out.println("Finished blocking tasks: " + done.get());
}
}Daemon Status
Virtual threads are always daemon threads and cannot be made non-daemon. Platform threads default to inheriting their creator's daemon status but can be set explicitly.
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread v = Thread.ofVirtual().unstarted(() -> {});
Thread p = Thread.ofPlatform().unstarted(() -> {});
System.out.println("virtual daemon: " + v.isDaemon());
System.out.println("platform daemon: " + p.isDaemon());
}
}Priorities Ignored
Virtual threads always report a priority of NORM_PRIORITY, and setting priority has no effect. Scheduling is handled by the JVM, not the OS scheduler.
public class Main {
public static void main(String[] args) {
Thread v = Thread.ofVirtual().unstarted(() -> {});
v.setPriority(Thread.MAX_PRIORITY);
System.out.println("Priority is still: " + v.getPriority());
}
}Decision Guideline
A practical rule of thumb:
- Lots of tasks that wait on I/O? Use virtual threads.
- Heavy computation bounded by CPU? Use a fixed pool of platform threads.
- Need OS-level thread tuning (priority, affinity)? Use platform threads.
They Coexist
You do not have to pick one for the whole program. A typical app uses virtual threads for request handling and a small platform-thread pool for CPU-intensive sections.
Both share the same Thread API, so mixing them is seamless.
Migration Tip
When moving an I/O-heavy service, the smallest change is swapping the executor: replace a fixed or cached pool with newVirtualThreadPerTaskExecutor().
Just be careful about pinning and ThreadLocal abuse, covered next lesson.
Quick Check
Choose the workload where virtual threads provide the clearest benefit.
Recap
You compared the two thread models:
- Platform threads: heavy, OS-backed, best for CPU-bound work and OS tuning
- Virtual threads: cheap, JVM-managed, always daemon, best for I/O-bound concurrency
- They coexist and share the same API
Next: the pitfalls of pinning and ThreadLocals.
Frequently asked questions
Is the “Platform vs Virtual Threads” lesson free?
Yes — the full text of “Platform vs Virtual Threads” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “Platform vs Virtual Threads”?
When each is appropriate. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Platform vs Virtual Threads” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- What Are Virtual Threads
- Creating Virtual Threads
- Platform vs Virtual Threads
- Pitfalls: Pinning and ThreadLocals