Java 21 Features Overview and Examples
Java 21 Features Overview and Examples
JAVA 21 FEATURES
(WITH EXAMPLES)
Sven Woltmann
On September 19, 2023, major launch events celebrated the release of Java 21, the
latest long-term support (LTS) version (after Java 17). Oracle will provide free
upgrades for at least five years, until September 2028 – and extended paid support
until September 2031.
✔ One of the most significant innovations in the history of Java, Virtual Threads, has
been finalized.
✔ Also finalized were two new Java language features from Project Amber: Record
Patterns and Pattern Matching for switch.
✔ A new, convenient interface, SequencedCollection, provides direct access to an
ordered collection’s first and last elements.
✔ Two long-awaited features that other languages have offered for a long time are
finally available in Java (for now as a preview feature): String Templates and
Unnamed Patterns and Variables.
[Link] 1/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Contents [ hide ]
1 Virtual Threads – JEP 444
1.1 What Are Virtual Threads?
1.2 Changes From the Preview Version
8 Summary
For several years, clever developers have been working on a better solution within
the scope of Project Loom. In Java 19, the time had finally come: Virtual threads were
introduced as a preview feature.
In Java 21, virtual threads are finalized via JDK Enhancement Proposal 444 and are
thus ready for production use.
Unlike reactive code, virtual threads allow programming in the familiar, sequential
thread-per-request style.
[Link] 3/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Sequential code is not only easier to write and read but also easier to debug since we
can use a debugger to trace the program flow step by step, and stack traces reflect
the expected call stack. Anyone who has ever tried debugging a reactive application
will understand what I mean.
Writing scalable applications with sequential code is made possible by allowing many
virtual threads to share a platform thread (the name given to the conventional
threads provided by the operating system). When a virtual thread has to wait or is
blocked, the platform thread will execute another virtual thread.
That allows us to run several million (!) virtual threads with just a few operating
system threads.
The best part is that we don’t have to change existing Java code. We simply tell our
application framework to use virtual threads instead of platform threads.
If you want to know precisely how virtual threads work, their limitations, and what
happens behind the scenes, you can read all about them in the main article on virtual
threads.
In the preview versions, it was possible to configure a virtual thread so that it cannot
have ThreadLocal variables (since these can be very expensive, virtual threads should
instead use Scoped Values, also delivered in Java 21 as a preview feature). This
possibility was removed again so that as much existing code as possible can run in
virtual threads without changes.
[Link] 4/44
13/01/2026, 18:58 Java 21 Features (with Examples)
In Java 21, we can finally replace this behemoth with a short and concise call:
Perhaps you’ve also needed to access the first element of a LinkedHashSet? Until
now, this required the following detour:
To access the last element of a LinkedHashSet, you even had to iterate over the
complete set! This can now also be done easily with getLast().
SequencedCollection Interface
In order to enable new, uniform methods for accessing the elements of a collection
with a stable iteration order, Java 21 introduced the interface SequencedCollection.
This defines, among others, the two methods getFirst() and getLast() presented above
and is inherited or implemented by those interfaces whose elements have the above-
mentioned stable iteration order:
[Link] 5/44
13/01/2026, 18:58 Java 21 Features (with Examples)
SequencedCollection reversed();
This method returns a view on the collection in reverse order. We can use this view to
iterate backward over the collection. “View” means that changes to the original
collection are visible in the view and vice versa.
SequencedSet Interface
The following figure shows how SequencedCollection and SequencedSet have been
inserted into the existing class hierarchy (for clarity, only a selection¹ of classes is
shown):
[Link] 6/44
13/01/2026, 18:58 Java 21 Features (with Examples)
¹ The selection is limited to those classes that are used at least 100 times in the JDK source
code.
SequencedMap Interface
In Java, collections (e.g., List, Set) and maps (e.g., HashMap) represent two separate
class hierarchies. For ordered maps (i.e., those whose elements have a defined
order), another new interface, SequencedMap, offers easy access to the first and last
element of such a map.
Entry<K, V> firstEntry() – returns the first key-value pair of the map
Entry<K, V> lastEntry() – returns the last key-value pair of the map
Entry<K, V> pollFirstEntry() – removes the first key-value pair and returns it
Entry<K, V> pollLastEntry() – removes the last key-value pair and returns it
[Link] 7/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Here you can see how the new interface was inserted into the existing class hierarchy
(this time with all implementing classes):
[Link] 8/44
13/01/2026, 18:58 Java 21 Features (with Examples)
The Collections utility class has been extended with some static utility methods,
specifically for sequenced collections:
Free Bonus:
[Link] 9/44
13/01/2026, 18:58 Java 21 Features (with Examples)
The Ultimate
Java Versions
PDF Cheat Sheet
The features of each Java version on a single page¹
Save time and effort with this compact overview of all new Java features from
Java 25 back to Java 10. In this practical and exclusive collection, you‘ll find the most
important updates of each Java version summarized on one page each.
First name...
Email address...
This sounds more complicated than it is. The best way to explain record patterns is
with an example:
We’ll start with a simple record (if you’re unfamiliar with records, you can find an
introduction to Java records here).
[Link] 10/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Now let’s assume we have an arbitrary object and want to perform a particular action
with it depending on its class – for instance, print something on the console.
We could do that using Pattern Matching for instanceof, introduced in Java 16, as
follows:
Instead of the pattern Position p, we can now also match a so-called record pattern –
namely Position(int x, int y) – and then access the variables x and y directly in the
following code instead of using p.x() and p.y():
We can also write the first example (the one without a record pattern) using Pattern
Matching for switch, which is also finalized in Java 21:
[Link] 11/44
13/01/2026, 18:58 Java 21 Features (with Examples)
We can not only match to a record whose fields are objects or primitives. We can also
match on a record whose fields are also records.
As an example, let’s add the following record, Path, with a start position and an end
position:
We want the print() method from the previous examples now also be able to print a
Path – here is the implementation without a record pattern:
[Link] 12/44
13/01/2026, 18:58 Java 21 Features (with Examples)
With a record pattern we could, for one, match on Path(Position from, Position to):
In the examples so far, the notation with record patterns does not bring a
considerable advantage. Record patterns can show their true strength when used
with records whose elements can have different types.
[Link] 13/44
13/01/2026, 18:58 Java 21 Features (with Examples)
We modify the print() method to display something different for a 3D path than for a
2D path. That is pretty easy to accomplish:
However, it was only that easy because we started with the variant with record
patterns!
// other cases
}
}
This time the variant with record patterns is much more concise! And the deeper the
nesting, the greater the advantage of using record patterns.
Record Patterns have been finalized with JDK Enhancement Proposal 440 – with one
change from the last preview version:
Java 20 introduced the ability to use record patterns in for loops as well, as in the
following example:
This option was removed in the final version of the feature, with the prospect of
reintroducing it in a future Java release.
switch (obj) {
case String s when [Link]() > 5 -> [Link]([Link]());
case String s -> [Link]([Link]()
case Integer i -> [Link](i * i);
case Position(int x, int y) -> [Link](x + "/" + y);
[Link] 15/44
13/01/2026, 18:58 Java 21 Features (with Examples)
default -> {}
}
Without Pattern Matching for switch, we would have to write the following less
expressive code instead (thanks to Pattern Matching for instanceof, introduced in Java
16, it is reasonably readable without the need for an explicit cast):
In contrast, a default branch is not necessary if the switch covers all possibilities of a
sealed class hierarchy, as in the following example:
}
}
Since sealing ensures that only two Shape implementations exist – namely Rectangle
and Circle – a default branch would be superfluous here (but not forbidden; see
below).
If we were to extend Shape at some point, e.g., by a third record called Oval, the
compiler would recognize the switch expression as incomplete and respond with the
error message 'switch' statement does not cover all possible input values:
This way, we can ensure that if we extend the interface, we will also have to adapt all
switch expressions. Alternatively, we could include a default branch in advance. Then
the switch statement would continue to compile and execute the default branch for
Oval.
With JDK Enhancement Proposal 441, Pattern Matching for switch was finalized with
two changes compared to the last preview version:
Until Java 20, it was possible to put patterns in parentheses like this:
switch (obj) {
case (String s) when [Link]() > 5 -> [Link]([Link]()
case (String s) -> [Link]([Link]
[Link] 17/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Since the parentheses served no purpose, this option was removed in the final
version of the feature.
Until now, we could only implement a switch expression over enum constants using a
“guarded pattern” – i.e., a pattern combined with when.
I’ll show you what this means with an example. Here are two enums that implement a
sealed interface:
Until Java 20, we had to implement a switch over all possible directions as follows:
[Link] 18/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Not only is this notation confusing, but the exhaustion analysis does not kick in here,
i.e., even though we have implemented all possible cases, a default branch is
necessary. Otherwise, a compiler error occurs.
In Java 21, we can now formulate the same logic much more concisely:
The compiler also recognizes that all cases are covered and no longer requires a
default branch.
[Link] 19/44
13/01/2026, 18:58 Java 21 Features (with Examples)
String string = "the red brown fox jumps over the lazy dog";
String[] parts = [Link](" ", 5);
[Link]([Link](parts).collect([Link]("', '",
'the', ' ', 'red', ' ', 'brown', ' ', 'fox', ' ', 'jumps over the lazy dog
Both StringBuilder and StringBuffer have been extended by the following two
methods:
Here is an example that calls repeat(…) once with a string, once with a code point and
once with a character:
[Link](0x1f600, 5);
[Link]('!', 3);
[Link](sb);
Speaking of emojis... the following new methods are provided by the Character class:
isEmoji(int codePoint)
isEmojiComponent(int codePoint)
isEmojiModifier(int codePoint)
isEmojiModifierBase(int codePoint)
isEmojiPresentation(int codePoint)
isExtendedPictographic(int codePoint)
These methods check whether the passed Unicode code point stands for an emoji or
a variant of it. You can read exactly what these variants mean in Appendix A of the
Unicode Emoji Specification.
How many times have we written the following piece of code to ensure that a number
is in a given numeric range, or otherwise pushed in?
[Link] 21/44
13/01/2026, 18:58 Java 21 Features (with Examples)
From now on, we can use [Link](...) for exactly this purpose. The method comes
in the following four flavors:
These methods check whether value is in the range min to max. If value is less than
min, they return min; if value is greater than max, they return max.
int a = ...;
int b = ...;
[Link] 22/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Often we encounter the need to declare variables that ultimately go unused. Typical
examples include Exceptions, lambda parameters, and pattern variables.
try {
int number = [Link](string);
} catch (NumberFormatException e) {
[Link]("Not a number");
}
[Link] 23/44
13/01/2026, 18:58 Java 21 Features (with Examples)
In Java 22, unnamed variables and patterns provide a more elegant solution, allowing
the replacement of the names of unused variables or even the entire pattern with an
underscore (_):
try {
int number = [Link](string);
} catch (NumberFormatException _) {
[Link]("Not a number");
}
And the partial pattern Position position2 can also be replaced with _:
Unnamed patterns and variables are defined in JDK Enhancement Proposal 443. In
the JEP, you can find some more examples of using unnamed variables. You can find
further details and a deeper examination of these features in the main article about
unnamed variables and patterns.
When novice programmers write their first Java program, it usually looks like this:
And that is only if the class is in the “unnamed package.” Otherwise, a package
declaration is also required.
Experienced Java developers will recognize the elements of this program at first
glance. But beginners are overwhelmed by visibility modifiers, complex concepts like
classes and static methods, unused method arguments, and a “[Link]”.
Exactly that is possible in Java 21, thanks to JDK Enhancement Proposal 445! The
following code is a valid, complete Java program as of now:
void main() {
[Link]("Hello world!");
}
Since the feature is still in the preview stage, you need to compile and run the code as
follows:
[Link] 25/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Alternatively, you can run the program without explicitly compiling it:
You can find the latest version of this feature in the Compact Source Files and
Instance Main Methods section (that is what the feature will be called from Java 25) of
the article on the Java main method.
In Java 22, the concept of the “unnamed class” was changed to an “implicitly
declared class”.
By the way, the main() method still is in a class: the so-called “unnamed class.” This is
not an entirely new concept. There was already the “unnamed package” (a class
without a package declaration) and the “unnamed module” (a Java source code
directory without a “[Link]” file).
Just as named modules cannot access code in the unnamed module, and just as code
from named packages cannot access unnamed packages, code from named classes
cannot access unnamed classes.
The unnamed class may also have fields and other methods. The following is also a
valid and complete Java program:
void main() {
[Link](hello("world"));
}
[Link] 26/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Launch Protocol
In Java 22, the start launch protocol has been simplified, as many of the
variations of the main() method shown here are mutually exclusive anyway.
The main() method may, of course, still be marked as public static and contain the
String[] argument. It may also be only public or only static. Or protected.
Theoretically, a class can also contain two main() methods – for example, the
following would also be allowed:
In such a case, the so-called “launch protocol” decides which of the main() methods to
start. The launch protocol searches in the following order; the visibility modifier is
irrelevant (only private is not allowed):
So in the example above, the JVM would start the static method with no parameters
(launch priority 2).
[Link] 27/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Scoped Values are a modern alternative to ThreadLocal variables that can be used
well in the context of virtual threads.
The first two points also lead to cleaner and, thus, less error-prone program code.
Scoped Values were introduced in Java 20 as an incubator project. In Java 21, JDK
Enhancement Proposal 446 upgrades them to a preview project without further
changes.
You can learn how scoped values work in the main article about scoped values.
To divide a task into several subtasks to be processed in parallel, Java has so far
provided two high-level constructs:
[Link] 28/44
13/01/2026, 18:58 Java 21 Features (with Examples)
You can learn how this works in the main article about Structured Concurrency.
Structured concurrency was first introduced in Java 19 in the incubator stage and
extended in Java 20 to allow subtasks to inherit the parent thread’s scoped values
described in the previous section.
In Java 21, JDK Enhancement Proposal 453 changed the return type of
[Link](…) – the method that starts subtasks – from Future to
Subtask. This should emphasize the difference between structured concurrency and
the ExecutorService API.
For example, [Link]() waits for a result, while [Link]() must only be called
once a subtask is finished – otherwise the method throws an IllegalStateException.
And [Link]() returns a state specific to structured concurrency, while
[Link]() and isCancelled() do not.
Until now, anyone who wanted to access code outside the JVM (e.g., functions in C
libraries) or memory not managed by the JVM had to use the Java Native Interface
(JNI). Anyone who has ever done this knows how cumbersome, error-prone, and slow
JNI is.
A replacement for JNI has been in the works since Java 14, initially in incubator
projects. In Java 19, a united “Foreign Function & Memory API” was introduced as a
first preview version.
The following code shows how to obtain a handle to the strlen() method of the
standard C library, place the string “Happy Coding!” in native memory (i.e., outside the
Java heap), and then execute the strlen() method on that string:
[Link] 29/44
13/01/2026, 18:58 Java 21 Features (with Examples)
The code differs from the Java 20 variant only in one detail: The [Link]()
method was previously called openConfined().
You can compile and execute the small example program as follows:
Accessing native memory and calling native code is a rather specialized area. Very few
programmers will come into direct contact with it. Therefore, I will not go into further
detail at this point. You can find details about the current state of the FFM API in JDK
Enhancement Proposal 442.
In Java 21, the new Vector API is submitted as an incubator feature for the sixth
consecutive release through JDK Enhancement Proposal 448.
The Vector API will make it possible to perform mathematical vector operations
efficiently. A vector operation is, for example, a vector addition, as you may
remember from math classes:
Modern CPUs can perform such operations up to a particular vector size in a single
CPU cycle. The vector API will enable the JVM to map such operations to the most
efficient instructions of the underlying CPU architecture.
I will introduce the vector API in detail as soon as it has outgrown the incubator stage
and is available in the first preview version.
In Java 15, the Z Garbage Collector, ZGC for short, was introduced. ZGC promises
pause times of less than ten milliseconds – which is up to a factor of 10 less than the
pause times of the standard G1GC garbage collector.
Until now, ZGC made no distinction between “old” and “new” objects. However,
according to the “Weak Generational Hypothesis,” precisely this difference can have a
significant impact on the performance of an application.
According to this hypothesis, most objects die shortly after their creation, whereas
objects that have survived a few GC cycles tend to stay alive even longer.
Therefore, we had to wait until Java 21 for JDK Enhancement Proposal 439 to make
the Z Garbage Collector a generational one.
For a transition period, both variants of the ZGC will be available. The VM option -
XX:+UseZGC still activates the old non-generational variant. To activate the new
generational variant, you must specify the following VM options:
-XX:+UseZGC -XX:+ZGenerational
In one of the future Java versions, the generational variant will become the default.
You must then explicitly switch to the non-generational variant using -XX:-
[Link] 32/44
13/01/2026, 18:58 Java 21 Features (with Examples)
ZGenerational. Later still, the variant without generations and the ZGenerational
parameter are to be removed again.
You can read about how exactly Generational ZGC works in JEP 439.
Not only the Z Garbage Collector was made generational, but also the “Shenandoah
Garbage Collector,” also introduced in Java 15.
However, the new Shenandoah version is still in the experimental stage. You can
activate it with the following VM options:
-XX:+UnlockExperimentalVMOptions -XX:ShenandoahGCMode=generational
The changes are described in JDK Enhancement Proposal 404 – but quite superficially.
If you are interested in how a generational garbage collector works, I recommend
reading the detailed JEP 439 (Generational ZGC, from the previous section).
The 32-bit version of Windows 10 is hardly used anymore, support ends in October
2025, and Windows 11 – on the market since October 2021 – has never been offered
in a 32-bit version.
Accordingly, there is hardly any need for a 32-bit Windows version of the JDK.
To speed up the development of the JDK, virtual threads have not been implemented
for 32-bit Windows. Anyone who tries to start a virtual thread on 32-bit Windows will
get a platform thread instead.
JDK Enhancement Proposal 449 marks the 32-bit Windows port as “deprecated for
removal.” It is to be removed entirely in a future release.
[Link] 33/44
13/01/2026, 18:58 Java 21 Features (with Examples)
If you have ever used a Java profiler, you probably started the application to be
analyzed with a parameter like -agentpath:<path-to-agent-library>. This loads a so-
called “agent” into the application, which modifies it at runtime to perform the
necessary measurements and either write the results to a file or send them to the
profiler’s front end.
If the application was started without this parameter, the agent can also be “injected”
into the JVM afterward using the so-called “Attach API.”
In a future Java version, dynamic loading will be disabled by default and can only be
explicitly enabled via the VM option -XX:+EnableDynamicAgentLoading.
Since such a change cannot be made overnight, dynamic loading is still allowed in
Java 21 but can be disabled with -XX:-EnableDynamicAgentLoading. In addition,
warnings are now displayed when an agent is loaded via the Attach API.
This change is defined in JDK Enhancement Proposal 451. There you will also find a
comprehensive list of security risks.
Through JDK Enhancement Proposal 452, the JDK provides an API for KEM.
Very few of us are directly confronted with implementing encryption and decryption
on a low level. Generally, we use it only indirectly, for example, by using SSH or
[Link] 34/44
13/01/2026, 18:58 Java 21 Features (with Examples)
For this reason, I will not go into more detail about this JEP.
When calling [Link](millis, nanos), the nanos value was virtually ignored until
now. It was only when nanos was greater than 500,000 (i.e., half a millisecond) that
the millis value was incremented by one, and then [Link](millis) was called.
As of Java 21, at least on Linux and macOS, the wait time is passed to the operating
system (or to the “unparker” in the case of a virtual thread) at nanosecond
granularity. The actual waiting time still depends on the precision of the system clock
and the scheduler.
(No JEP exists this change, it is registered in the bug tracker under JDK-8305092.)
When using the G1 garbage collector (G1GC), the available heap memory is divided
into up to 2,048 regions. Objects larger than half of such a region are called
“humongous objects.”
Starting from Java 21, also humongous objects are relocated – however, only if, after a
full GC, there’s still insufficient contiguous memory available. This process can take
quite long (up to several seconds) depending on the size of the heap.
(No JEP exists this change, it is registered in the bug tracker under JDK-8191565.)
When a thread enters a synchronized block on an object, the JVM must store this
information somewhere to prevent another thread from entering the critical section.
Until now, this has been done using a mechanism called “stack locking”. Here, the
object header's Mark Word is replaced by a pointer to a data structure on the stack,
which in turn contains the Mark Word and further information about the lock state.
Firstly, this mechanism makes it more difficult to access the actual data of the Mark
Word. Secondly, the pointer to the stack is one reason for the so-called pinning of
virtual threads.
Lightweight locking can be activated using the VM option described in the next
section.
(No JEP exists this change, it is registered in the bug tracker under JDK-8291555.)
1. When a thread enters a critical section, only the information that the section is
locked is stored in the monitor object (the object that is specified in parentheses
behind synchronized) – further information is not necessary at this time.
2. Only when another thread tries to enter the critical section, a list of waiting
threads must be created, among other things. For this purpose, an additional
data structure is created, the so-called “heavyweight monitor”.
Step 1 was previously performed by the so-called “stack locking”. Using the VM option
-XX:+UseHeavyMonitors, step 1 could be skipped and the “heavyweight monitor”
created directly.
[Link] 36/44
13/01/2026, 18:58 Java 21 Features (with Examples)
To activate the new locking mechanism described in the previous section, Java 21
introduces the new VM option -XX:LockingMode, with the following options:
Since the feature is currently still in the experimental stage, you must also specify the
VM option -XX:+UnlockExperimentalVMOptions.
In Java 23, the new lightweight locking will become the default mode.
In Java 24, the VM option -XX:LockingMode will be marked as “deprecated”, in Java 26,
it will be disabled, and in Java 27, it will be removed again.
(No JEP exists this change, it is registered in the bug tracker under JDK-8305999.)
In this article, you have learned about all JDK Enhancement Proposals delivered in
Java 21. You can find additional minor changes in the official Java 21 Release Notes.
Summary
The new LTS release Java 21 brings one of the most significant changes in Java history
with the finalization of virtual threads, which will significantly simplify the
[Link] 37/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Unnamed Classes and Instance Main Methods (also in the preview stage) make it
easier for programmers to get started with the language without having to
understand complex constructs like classes and static methods right at the beginning.
Various other changes complement the release as usual. You can download the latest
Java 21 version here (OpenJDK) and here (Oracle).
Which Java 21 feature are you most looking forward to? Which feature do you miss?
Let me know via the comment function!
If this article has helped you, I would greatly appreciate a positive review on my
ProvenExpert profile. Your feedback helps me improve my content and motivates me
to write new informative articles.
👉 Leave a review
You want to be up to date on all new Java features? Then click here to sign up for the
HappyCoders newsletter.
👉 Newsletter Sign-up
Previous:
New features in Java 20
Next:
New features in Java 22
[Link] 38/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Free Bonus:
The Ultimate
Java Versions
PDF Cheat Sheet
The features of each Java version on a single page¹
Save time and effort with this compact overview of all new Java features from
Java 25 back to Java 10. In this practical and exclusive collection, you‘ll find the most
important updates of each Java version summarized on one page each.
First name...
Email address...
[Link] 39/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Leave a Reply
Your email address will not be published. Required fields are marked *
Comment *
Name *
Email *
[Link] 40/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Post Comment
JAVA 26 FEATURES
(WITH EXAMPLES)
[Link] 41/44
13/01/2026, 18:58 Java 21 Features (with Examples)
[Link] 42/44
13/01/2026, 18:58 Java 21 Features (with Examples)
JAVA 25 FEATURES
(WITH EXAMPLES)
Blog Resources
Java Java Versions Cheat Sheet
Algorithms and Data Structures Big O Cheat Sheet
Software Craftsmanship Newsletter
Book Recommendations
Services About
Java Training Courses About Sven Woltmann
[Link] 43/44
13/01/2026, 18:58 Java 21 Features (with Examples)
Follow HappyCoders
[Link] 44/44