Java 21 features

8 minute read

dist files

Foto de Merlene Goulet en Unsplash

Java latest version was released on 19 September 2023 and it has been some time since this new LTS was made available in production.

In this blog post, we are going to talk about which are the new features of this new Java JDK.

There are features that are in preview status in this Java JDK release and are not permanent, waiting for developer feedback in real-world usage. If you want to use them you need to do the following:

Compile your app:

$> javac --release 21 --enable-preview

Run it:

java --enable-preview MyApp

1 - String Templates (Preview)

Syntactically, a template expression resembles a string literal with a prefix. There is a template expression on the second line of this code:

String name = "Alex";
String info = STR."My name is \{name}";
assert info.equals("My name is Alex");   // true

The template expression STR."My name is \{name}" consists of:

  • A template processor (STR);
  • A dot character (U+002E), as seen in other kinds of expressions;
  • A template ("My name is \{name}") which contains an embedded expression (\{name})`.

More info of this feature in this link

2 - Sequenced Collections

A SequencedCollection is a Collection whose elements have a defined encounter order. This type of collection has first and last elements, and the elements between them have successors and predecessors.

A SequencedCollection supports common operations at either end, and it supports processing the elements from first to last and from last to first (i.e., forward and reverse).

interface SequencedCollection<E> extends Collection<E> {
   // new method
   SequencedCollection<E> reversed();
   // methods promoted from Deque
   void addFirst(E);
   void addLast(E);
   E getFirst();
   E getLast();
   E removeFirst();
   E removeLast();

dist files

From the previous picture we can see the following points:

  • List now has SequencedCollection as its immediate superinterface,
  • Deque now has SequencedCollection as its immediate superinterface,
  • LinkedHashSet additionally implements SequencedSet,
  • SortedSet now has SequencedSet as its immediate superinterface,
  • LinkedHashMap additionally implements SequencedMap, and
  • SortedMap now has SequencedMap as its immediate superinterface

Foto de Merlene Goulet en Unsplash

More info of this feature in this link

3 - Generational ZGC

The purpose of this feature is to improve application performance by extending the Z Garbage Collector (ZGC) to maintain separate generations for young and old objects. This will allow ZGC to collect young objects — which tend to die young — more frequently.

More info of this feature in this link

4 - Record Patterns

First, we need to recall the concept of Record which are classes that act as transparent carriers for immutable data.

In the following code, it would be better if the pattern could not only test whether a value is an instance of Point but also extract the x and y components from the value directly, invoking the accessor methods on our behalf.

// As of Java 16

record Point(int x, int y) {}

static void printSum(Object obj) {
   if (obj instanceof Point p) {
       int x = p.x();
       int y = p.y();

Let’s see how this has been improved in Java 21:

// As of Java 21

static void printSum(Object obj) {
   if (obj instanceof Point(int x, int y)) {

Point(int x, int y) is a record pattern. It lifts the declaration of local variables for extracted components into the pattern itself and initializes those variables by invoking the accessor methods when a value is matched against the pattern

More info of this feature in this link

5 - Pattern Matching for switch (Fourth preview)

This feature is related to improving pattern matching for switch expressions and statements. Let’s check the following code:

// Prior to Java 21
static String formatter(Object obj) {
    String formatted = "unknown";
    if (obj instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (obj instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (obj instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (obj instanceof String s) {
        formatted = String.format("String %s", s);
    return formatted;

// As of Java 21
static String formatterPatternSwitch(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> obj.toString();

A case label with a pattern applies if the value of the selector expression obj matches the pattern. The intent of this code is clearer because we are using the right control construct: We are saying, “the parameter obj matches at most one of the following conditions, figure it out and evaluate the corresponding arm.”

More info of this feature in this link

6 - Foreign Function & Memory API (Third Preview)

With this feature the idea is to have an API to interoperate with code and data on the same machine as the JVM, but outside the Java runtime. This API will allow Java programs to interoperate with code and data outside of the Java runtime.

Invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI (Java Native interface)

More info about this new Java 21 feature can be found here

7 - Unnamed Patterns and Variables (Preview)

In the following code we are using Record pattern feature previously described, but note that we are not using Color inside the if expression.

if (r instanceof ColoredPoint(Point p, Color c)) {
    ... p.x() ... p.y() ...

with unnamed patterns we could do something like the following code snippet. Note the underscore that we are using for Color c

if (r instanceof ColoredPoint(Point(int x, int y), _)) { ... x ... y ... }

In the following example, the order variable is not used:

int total = 0;
for (Order order : orders) {
    if (total < LIMIT) { 
        ... total++ ...

The corresponding unnamed variable would be with _

int acc = 0;
for (Order _ : orders) {
    if (acc < LIMIT) { 
        ... acc++ ...

More info of this feature in this link

8 - Virtual Threads

This might be the most interesting feature of this JDK release. Until now the number of available threads is limited because the JDK implements threads as wrappers around operating system (OS) threads.

OS threads are costly, so we cannot have too many of them. Threads until now have become the limiting factor long before other resources, such as CPU or network connections, are exhausted.

Now is possible with virtual threads to scale with near-optimal hardware utilization.

Virtual threads are simply threads that are cheap to create and almost infinitely plentiful. Hardware utilization is close to optimal, allowing a high level of concurrency and, as a result, high throughput.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
   IntStream.range(0, 10_000).forEach(i -> {
       executor.submit(() -> {
           return i;
}  // executor.close() is called implicitly, and waits

More info in the following link

9 - Unnamed Classes and Instance Main Methods (Preview)

The idea of this feature is that students can write their first programs without needing to understand language features designed for large programs. When you create your first Java program you need to obviate some concepts like class, static, or the String []. With this preview feature instead of doing this code:

public class HelloWorld {
   public static void main(String[] args) {
       System.out.println("Hello, World!");

Students will write the following code:

void main() {
   System.out.println("Hello, World!");

More info in the following link

10 - Scoped Values (Preview)

Thread-local variables have more complexity than is usually needed for sharing data, and significant costs that cannot be avoided.

The idea behind scoped values is to maintain inheritable per-thread data for thousands or millions of virtual threads.

These per-thread variables are immutable, their data can be shared by child threads efficiently.

Further, the lifetime of these per-thread variables will be bounded: Any data shared via a per-thread variable will become unusable once the method that initially shared the data is finished.

private static final ScopedValue<String> X = ScopedValue.newInstance();

void foo() {
   ScopedValue.where(X, "hello").run(() -> bar());

void bar() {
   System.out.println(X.get()); // prints hello
   ScopedValue.where(X, "goodbye").run(() -> baz());
   System.out.println(X.get()); // prints hello

void baz() {
   System.out.println(X.get()); // prints goodbye

More info in this link

11 - Vector API (Sixth Incubator)

With this feature, the intention is to introduce an API to express vector computations that reliably compile at runtime to optimal vector instructions on supported CPU architectures, thus achieving performance superior to equivalent scalar computations.

More info in this link

12 - Deprecate the Windows 32-bit x86 Port for Removal

Windows 10, the last Windows operating system to support 32-bit operation, will reach End of Life in October 2025 the idea is to deprecate the Windows 32-bit x86 port, with the intent to remove it in a future release.

More info in this link

13 - Prepare to Disallow the Dynamic Loading of Agents

An agent is a component that can alter the code of an application while the application is running. Agents were introduced by the Java Platform Profiling Architecture in JDK 5 as a way for tools, notably profilers, to instrument classes. If the agents are loaded at startup will not cause warnings as if they were loaded into a running JVM. All this in order to improve integrity by default.

More info in this link

14 - Key Encapsulation Mechanism API

KEMs will be an important tool for defending against quantum attacks. None of the existing cryptographic APIs in the Java Platform is capable of representing KEMs naturally.

Implementors of third-party security providers have already expressed a need for a standard KEM API.

With this new Java JDK we will be able to use an API for key encapsulation mechanisms (KEMs), an encryption technique for securing symmetric keys using public key cryptography.

More info in this link

15 - Structured Concurrency (Preview)

Simplify concurrent programming by introducing an API for structured concurrency. Structured concurrency treats groups of related tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability.

More info in this link


In this post we have seen a short explanation of each one of the new Java 21 features. Which is the feature that surprised you more? are you planning to migrate to Java 21?

I won't give your address to anyone else, won't send you any spam, and you can unsubscribe at any time.
Disclaimer: Opinions are my own and not the views of my employer