Discovering Java 21’s Multithreading Features: A Journey into Virtual Threads, Structured Concurrency, and More

As I dove into Java 21’s new multithreading capabilities, I found myself on an exciting learning journey. While concurrency is nothing new, Java 21 introduces some fascinating concepts like Virtual Threads and Structured Concurrency that are worth exploring. I invite you to join me as we take this step-by-step approach together, learning how these features reshape the way we handle concurrency in Java.


1. Virtual Threads: A Game-Changer for Concurrency

I’ve worked with Java threads before, but the introduction of Virtual Threads blew my mind. At first, I wondered: “What makes virtual threads different from the traditional threads I’ve been using all this time?”

What I Learned About Virtual Threads

It turns out that virtual threads are lightweight, user-mode threads that run on the Java runtime itself, rather than being mapped directly to OS threads. This allows them to scale far beyond the limits of traditional threads.

What amazed me was the fact that virtual threads make it possible to create millions of concurrent tasks without worrying about system overhead. It’s a shift from managing thread pools and thread counts to just letting each task have its own thread. This sounded too good to be true at first, but then I tried it out:

Example I Played With:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        // Task logic here
    });
}

I was impressed by how easy it was to create virtual threads and how familiar it felt. No special syntax, no complex setup—just regular threads but way more efficient. It got me thinking about how much simpler concurrency management could become for larger, high-load applications.


2. Structured Concurrency: Bringing Order to Parallel Tasks

After virtual threads, the next thing that caught my attention was Structured Concurrency. I’ll admit, at first, I wasn’t entirely sure how this was different from my regular approach to task management. But as I explored further, the value started to reveal itself.

What I Discovered About Structured Concurrency

Structured Concurrency treats multiple parallel tasks as one cohesive unit of work. The moment I understood this, I realized how useful this could be. Instead of managing individual tasks and hoping everything runs smoothly, structured concurrency guarantees that tasks are managed together in a single scope—errors, cancellations, and all.

When I tried the following code snippet, I saw how it could help clean up my concurrent code:

Code I Experimented With:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<String> task1 = scope.fork(() -> {
        // Task 1 logic
    });
    Future<Integer> task2 = scope.fork(() -> {
        // Task 2 logic
    });
    scope.join();           // Wait for all tasks
    scope.throwIfFailed();  // Handle exceptions
    String result1 = task1.resultNow();
    Integer result2 = task2.resultNow();
}

This approach forced me to think of parallel tasks as parts of a whole. It reminded me of how I sometimes struggled with error handling in concurrent code—structured concurrency solves that by allowing me to handle all failures in one place. It’s like having a safety net for parallel execution, which was a big relief.


3. Scoped Values: A Better Way to Pass Data Between Tasks

Before diving into this, I had a preconceived notion that Scoped Values were just another variant of thread-local variables. But the more I explored, the clearer the distinction became.

What I Found Out About Scoped Values

Scoped values are much safer than thread-local variables because they’re tied to the scope of execution rather than the thread itself. This made so much sense when I thought about how easy it is to make mistakes with thread-local storage, especially when managing many concurrent tasks (virtual threads or not).

What really clicked for me was the way scoped values make data sharing between tasks explicit and safe. Unlike thread-local variables, which could sometimes feel a bit like hidden state, scoped values made the flow of data between tasks crystal clear.

A Simple Example I Tried:
ScopedValue<String> currentUser = ScopedValue.newInstance();
ScopedValue.where(currentUser, "Alice").run(() -> {
    // currentUser is "Alice" in this scope
});

This tiny bit of code highlighted how scoped values improve both clarity and safety. By explicitly defining the scope in which data is valid, I found it much easier to reason about the flow of information through my tasks.


4. Thread-Local Variables: Still Useful, But with a Twist

As much as I was excited about scoped values, I realized that Thread-Local Variables still play an important role, especially for legacy applications or existing codebases. I had worked with them before, but with virtual threads in the mix, I realized that thread-local storage might not always be the best fit.

What Changed My Perspective on Thread-Local Variables

The introduction of virtual threads poses challenges for thread-local variables because of the massive number of threads being created. I learned that it’s important to be cautious about overusing thread-local variables in this context, but they’re still valuable when used thoughtfully.

My Experiment with Thread-Local Variables:
ThreadLocal<String> myThreadLocal = ThreadLocal.withInitial(() -> "Initial Value");
String value = myThreadLocal.get();

This is a reminder that while new features like scoped values bring better safety, thread-local variables aren’t obsolete—they just need to be managed more carefully in the new world of virtual threads.


What I Took Away from This Journey

Java 21’s multithreading features have opened up new ways of thinking about concurrency. Virtual Threads bring scalability to a whole new level, while Structured Concurrency helps bring order to parallel execution. Scoped Values offer a safer, clearer alternative to thread-local variables, and while thread-local variables still have their place, they require more thought in virtual thread environments.

I came into this exploration knowing that Java 21 was introducing new concurrency features, but I didn’t expect to learn so much about how they would reshape the way I write concurrent programs. It’s been a journey of discovery, and I hope this post gives you a sense of how these tools can simplify and improve your own work with concurrency.


Have you started exploring these new features yourself? I’d love to hear your thoughts and experiences! Let’s continue learning together in the comments.

📚 Further Reading & Related Topics

If you’re exploring Java 21’s multithreading features, including virtual threads and structured concurrency, these related articles will provide deeper insights:

• Mastering Concurrency in Java: Best Practices for Thread Management – Learn how to effectively manage threads and concurrency in Java applications, incorporating best practices from both older and newer Java versions.

• The Future of Java: A Deep Dive into New Features and Enhancements – Explore how Java’s latest updates, including virtual threads and other concurrency improvements, streamline development for scalable and performant applications.

2 responses to “Discovering Java 21’s Multithreading Features: A Journey into Virtual Threads, Structured Concurrency, and More”

  1. Java 25 LTS Release: What to Expect in September 2025 – Scalable Human Blog Avatar

    […] This post provides background on features that are likely to be refined or expanded in Java 25. • Discovering Java 21’s Multithreading Features: A Journey into Virtual Threads, Structured Concurre… – Dive deeper into Java 21’s groundbreaking multithreading enhancements, which are foundational […]

    Like

  2. Optimise Your Async Processing with Thread Pools: A Cost-Effective Approach – Scalable Human Blog Avatar

    […] thread pool usage by improving readability and lifecycle management of concurrent tasks. • Discovering Java 21’s Multithreading Features: A Journey into Virtual Threads, Structured Concurre… – Learn about Java 21’s advancements in multithreading, including virtual threads, which offer […]

    Like

Leave a comment

I’m Sean

Welcome to the Scalable Human blog. Just a software engineer writing about algo trading, AI, and books. I learn in public, use AI tools extensively, and share what works. Educational purposes only – not financial advice.

Let’s connect