Java 21 Features Explained: The Complete Guide with Code Examples
Java 21, released in September 2023, is one of the most important Java releases in years — and not just because it's a Long-Term Support (LTS) version. Between Virtual Threads, Record Patterns, Pattern Matching for switch
, Sequenced Collections, and a dramatically improved garbage collector, Java 21 changes how we write concurrent, modern, expressive code.
In this guide, we cover every major Java 21 feature with working code examples, explain which features are final vs. preview, and help you decide whether your team should upgrade.
- Java 21 Final Features (Production-Ready)
- 1. Virtual Threads (JEP 444)
- 2. Sequenced Collections (JEP 431)
- 3. Record Patterns (JEP 440)
- 4. Pattern Matching for switch (JEP 441)
- 5. Generational ZGC (JEP 439)
- 6. Key Encapsulation Mechanism API (JEP 452)
- 7. Preview & Incubator Features
- Java 21 Features: Quick Reference Table
- Should You Migrate to Java 21?
- Frequently Asked Questions
Java 21 Final Features (Production-Ready)
Java 21 ships 12 JDK Enhancement Proposals (JEPs). Six of them are final features you can use in production today — no preview flags required.
1. Virtual Threads (JEP 444) ⭐ The Headline Feature
Virtual threads are lightweight threads managed by the JVM rather than the operating system. While a platform (OS) thread costs around 1–2 MB of stack and maps 1:1 to an OS thread, you can run millions of virtual threads on a single machine.
The Problem It Solves
For decades, high-throughput Java servers had two bad options:
- Thread-per-request (simple code, but threads are expensive → limited concurrency), or
- Reactive/asynchronous (great scalability, but complex "callback hell" code that's hard to debug).
Virtual threads give you both: write simple, synchronous, blocking code — and the JVM suspends and reschedules virtual threads automatically when they block, so hardware is never wasted waiting.
Code Example
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -⟩ {
executor.submit(() -⟩ {
Thread.sleep(Duration.ofSeconds(1)); // blocking is cheap now!
return i;
});
});
} // 10,000 concurrent "threads", executed on a handful of OS threads
Or create one directly:
Thread.startVirtualThread(() -⟩ System.out.println("Hello from a virtual thread!"));
// or Thread.ofVirtual().name("vt-1").start(runnable);
System.out.println(Thread.currentThread().isVirtual()); // true inside the VT
Important Notes
- Don't pool virtual threads. They are so cheap you create one per task, not per reusable worker.
- ThreadLocal beware: millions of virtual threads × large ThreadLocal data = memory blowups.
- Use
Thread.ofVirtual()
,Executors.newVirtualThreadPerTaskExecutor()
, or simply rely on frameworks — Spring Boot 3.2+ supports virtual threads with one property:spring.threads.virtual.enabled=true
. - Great for Spring Boot, Tomcat, Netty (when blocking), REST clients, JDBC — anything I/O-bound.
2. Sequenced Collections (JEP 431)
Java's collection hierarchy had a long-standing gap: no common supertype for collections with a well-defined encounter order (List
, Deque
, SortedSet
have order; Set
, HashMap
generally don't). Reversing a SortedSet
required clumsy code.
Java 21 introduces three new interfaces:
SequencedCollection⟨E⟩SequencedSet⟨E⟩SequencedMap⟨K,V⟩
New Uniform Methods
interface SequencedCollection⟨E⟩ extends Collection⟨E⟩ {
SequencedCollection⟨E⟩ reversed(); // view in reverse order
void addFirst(E e); void addLast(E e);
E getFirst(); E getLast();
E removeFirst(); E removeLast();
}
Code Example
SortedSet⟨String⟩ names = new TreeSet⟨⟩(Set.of("Ana", "Raj", "Zoe"));
System.out.println(names.getFirst()); // Ana
System.out.println(names.reversed()); // [Zoe, Raj, Ana]
LinkedHashMap⟨String, Integer⟩ scores = new LinkedHashMap⟨⟩();
scores.putFirst("gold", 3); // now exists for maps too
scores.putLast("bronze", 1);
System.out.println(scores.sequencedKeySet().reversed()); // [bronze, gold]
3. Record Patterns (JEP 440)
Records (Java 16) gave us compact data carriers. Record patterns let you destructure them — extract components directly inside instanceof
and switch
, enabling nested, declarative data navigation.
Before Java 21
// Verbose: cast + manual accessor calls
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
// ...
}
With Java 21
record Point(int x, int y) {}
record ColoredPoint(Point p, String color) {}
record Rectangle(ColoredPoint topLeft, ColoredPoint bottomRight) {}
static void printUpperLeft(Object obj) {
if (obj instanceof Rectangle(
ColoredPoint ul, ColoredPoint br)) { // record pattern
System.out.println(ul.p().x() + "," + ul.p().y());
}
}
// Even nested destructuring in one shot:
if (obj instanceof Rectangle(
ColoredPoint(Point(var x, var y), var color1),
ColoredPoint(Point(var x2, var y2), var color2))) {
System.out.println("Corner A: " + x + "," + y + " " + color1);
}
4. Pattern Matching for switch (JEP 441)
The switch
expression finally becomes a powerful, type-safe pattern-matching construct. It works with null, supports guards, and the compiler enforces exhaustiveness — no more forgotten cases.
Code Example
sealed interface Shape permits Rectangle2, Circle, Triangle {}
record Rectangle2(double l, double w) implements Shape {}
record Circle(double radius) implements Shape {}
record Triangle(double base, double height) implements Shape {}
static String describe(Object obj) {
return switch (obj) {
case null -⟩ "nothing here";
case String s when s.isEmpty()
-⟩ "empty string";
case String s -⟩ "string of length " + s.length();
case Rectangle2(var l, var w) -⟩ "rect " + l + "x" + w; // record pattern!
case Circle c -⟩ "circle r=" + c.radius();
default -⟩ "something else";
};
}
Combined with sealed classes, the compiler can verify that a switch over a sealed hierarchy covers every permitted subtype — turning runtime surprises into compile-time errors.
5. Generational ZGC (JEP 439)
The Z Garbage Collector (ZGC), famous for sub-millisecond pause times, now uses a generational design by default. The key insight: most objects die young. By collecting young objects separately and more frequently, Generational ZGC delivers:
- Higher allocation throughput and lower allocation stalls
- Lower required heap memory overhead
- Pauses still well under 1 ms, even on multi-terabyte heaps
# Enable it explicitly (recommended) or it's default on many setups: java -XX:+UseZGC -XX:+ZGenerational ...
6. Key Encapsulation Mechanism (KEM) API (JEP 452)
A new standard API (java.security
) for KEM algorithms, an encryption technique used by post-quantum cryptography (e.g., NIST's upcoming ML-KEM). Most developers won't touch it daily, but it's a foundational piece for future-proof secure systems:
KEM kem = KEM.getInstance("ML-KEM-768"); // example provider algorithm
KEM.Encapsulated encapsulated = kem.newEncapsulator(publicKey).encapsulate();
SecretKey key = encapsulated.key(); // shared secret
byte[] ciphertext = encapsulated.encapsulation();
7. Preview & Incubator Features in Java 21
Java 21 also ships several preview/incubator features worth knowing (enable with --enable-preview
or incubator module flags):
- String Templates (JEP 430, 2nd preview): type-safe, injection-resistant string composition.
String name = "Joan"; String s = STR."Hello \{name}!"; - Unnamed Variables & Patterns (JEP 443, preview): use
_
for values you don't need —case Box(_)
ortry { ... } catch (Exception _) { }
(finalized in Java 22/23). - Structured Concurrency (JEP 453, preview): treat related concurrent tasks as one unit of work with
StructuredTaskScope
, simplifying error handling and cancellation. - Scoped Values (JEP 446, preview): a lightweight, immutable alternative to
ThreadLocal
that's virtual-thread friendly. - Vector API (7th incubator): platform-independent SIMD vector computations for numerical code.
- Foreign Function & Memory API (3rd preview): safe, pure-Java access to off-heap memory and native libraries — replacing JNI's pain (finalized in Java 22).
- Class-File API (JEP 457, preview): a standard API for parsing and generating class files (finalized in Java 24).
Java 21 Features: Quick Reference Table
| JEP | Feature | Status | Why It Matters |
|---|---|---|---|
| 444 | Virtual Threads | Final ✅ | Massive concurrency with simple blocking code |
| 431 | Sequenced Collections | Final ✅ | Uniform first/last/reversed API for ordered collections |
| 440 | Record Patterns | Final ✅ | Destructuring records; works with switch |
| 441 | Pattern Matching for switch | Final ✅ | Type-safe, exhaustive, null-friendly switches |
| 439 | Generational ZGC | Final ✅ | Lower memory footprint, same sub-ms pauses |
| 452 | Key Encapsulation Mechanism API | Final ✅ | Foundation for post-quantum crypto |
| 430 | String Templates | Preview ๐ถ | Safe, readable string interpolation |
| 443 | Unnamed Variables & Patterns | Preview ๐ถ | Cleaner code when values are unused |
| 453 | Structured Concurrency | Preview ๐ถ | Simpler, safer multi-task concurrency |
| 446 | Scoped Values | Preview ๐ถ | Virtual-thread-friendly alternative to ThreadLocal |
| 448 | Vector API | Incubator ๐งช | SIMD performance for compute-heavy code |
| 454 | Foreign Function & Memory API | Preview ๐ถ | Safe native interop without JNI |
Should You Migrate to Java 21?
✅ Reasons to upgrade
- LTS release — supported for years
- Virtual threads = big throughput wins for I/O-bound services
- Pattern matching + records = far less boilerplate
- Generational ZGC lowers cloud bills (memory)
- All libraries/frameworks (Spring Boot 3.x, Jakarta EE, Quarkus) fully support it
⚠️ Before you upgrade
- Security Manager is deprecated for removal
- Some older libs need updates (check ByteBuddy, Lombok, Mockito versions)
- Don't enable
--enable-preview
in production - Audit
ThreadLocal
usage before virtual threads
Frequently Asked Questions (FAQ)
Is Java 21 an LTS release?
Yes — Java 21 is an LTS release (September 2023), the successor LTS to Java 17.
What is the single biggest reason to move to Java 21?
For most server-side teams: Virtual Threads. You keep simple blocking code and gain the scalability that previously required reactive programming.
How do I enable preview features?
javac --release 21 --enable-preview MyApp.java
and java --enable-preview MyApp
. In Maven, set ⟨maven.compiler.release⟩21⟨/maven.compiler.release⟩
and add --enable-preview
via the compiler plugin compilerArgs
.
Are virtual threads faster than platform threads?
For CPU-bound work: no, they're the same. For I/O-bound workloads: yes — vastly better throughput per machine because blocking is nearly free.
Java 21 vs Java 17 — which should I use?
New projects: start on Java 21 (LTS). Existing Java 17 apps: plan the upgrade — it's generally low-risk and high-reward.
Conclusion
Java 21 is arguably the most impactful release since Java 8. Virtual Threads reshape server-side concurrency, record patterns and pattern matching make Java code radically more expressive, sequenced collections fix a 25-year-old API gap, and Generational ZGC makes memory-efficient, pause-free applications practical at scale.
If you found this guide useful, share it with your team — and drop a comment with which feature you're most excited to use!
Comments
Post a Comment