Java 21: what’s new ?

Java 21: what’s new ?

Now that Java 21 is features complete (Rampdown Phase Two at the day of writing), it’s time to walk through all the functionalities that bring to us, developers, this new version.

This article is part of a series on what’s new on the last versions of Java, for those who wants to read the others, here are the links: Java 20, Java 19, Java 18, Java 17, Java 16, Java 15, Java 14, Java 13, Java 12, Java 11Java 10, and Java 9.

Java 21 is the new Long Term Support (LTS) version , which means it will be supported for at least 5 years by Oracle. Given the number of JEPs (Java Enhancement Proposals) it contains – no fewer than 15 – it’s clear that this is a version rich in new features. But perhaps the most important (not to say exciting) one is the finalization of virtual threads! Virtual threads are lightweight threads with low creation and scheduling costs, making it easier to write concurrent applications. We’ll have to wait for the ecosystem to support them, but virtual threads will make Java relevant for highly concurrent applications in memory-constrained environments.

JEP 430 – String Templates (Preview)

Many languages support string interpolation. String interpolation is a string literal containing expressions as well as text literals.

For example, in Kotlin, "$x plus $y equals ${x + y}" is a string containing the expressions $x, $y, and ${x + y}, which will be replaced by their textual values. These values are said to be interpolated within the character string. This interpolation is carried out from the variables and enables operations between variables (in this case, an addition).

The problem with interpolation is that it’s dangerous as a global feature, because it doesn’t allow for validation or sanitization when constructing the final string. This exposes it, for example, to SQL or JavaScript injections.

In Java, String Templates offer string interpolation with validation and sanitization via a template processor.

A template processor takes a template, then interpolates the template to an object of a specific type; so from a template, you can interpolate a String, or a PreparedStatement, or a JSONObject, … As it is possible to have several processors, each can implement a validation step if required.

Here’s an example of how to use the String Template STR, which has no specific validation and can be used to replace string concatenation:

String firstName = "Loïc";
String lastName  = "Mathieu";
String helloWorld  = STR."Hello \{firstName} \{lastName}";

In Java, expressions are defined by \{expression}, and calling a template processor is done via its name, in this case FMT, which is a constant of the StringTemplate interface that will have been previously imported via a static import.

There has been a lot of discussion around the choice of the expression format. Due to the existence of many libraries using $, # or {} as expression delimiters, the choice was for a format that is not valid outside String Templates: String s = "Hello \{firstName} \{lastName}" does not compile. This distinguishes String Templates from simple Strings.

The standard library includes three template processors:

  • RAW: processor that does not interpolate character strings, enables low-level manipulations.
  • STR: processor that interpolates a character string to another character string via simple concatenation.
  • FMT: processor that interpolates a character string to another character string, allowing expressions to be formatted via a Formatter, for example FMT."%05d\{x} + %05d\{y} = %05d\{x + y}";

You can create your own processors by implementing the StringTemplate.Processor interface.

More information in the JEP 430.

JEP 431 – Sequenced Collections

Java’s Collection API has seen an addition in Java 21 of a magnitude not seen for many, many releases!

Java collections don’t have a type representing an ordered sequence of elements, Java 21 fills this gap by introducing the SequencedCollection, SequencedSet and SequencedMap interfaces. These interfaces provide methods for adding, modifying or deleting elements at the beginning or end of the collection, as well as for iterating over a collection in reverse order.

Here’s the SequencedCollection interface:

interface SequencedCollection<E> extends Collection<E> {
    SequencedCollection<E> reversed();
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

The reversed() method will return a view of the collection in reversed order; a modification to the original collection will impact the reversed view.

SequencedSet is a set that is also a SequencedCollection, here is its interface:

interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
    SequencedSet<E> reversed();
}

SequencedMap is a map whose entries are ordered, here is its interface:

interface SequencedMap<K,V> extends Map<K,V> {
    SequencedMap<K,V> reversed();
    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Entry<K,V>> sequencedEntrySet();
    V putFirst(K, V);
    V putLast(K, V);
    Entry<K,V> firstEntry();
    Entry<K,V> lastEntry();
    Entry<K,V> pollFirstEntry();
    Entry<K,V> pollLastEntry();
}

These new interfaces have been integrated into the existing Collection API class hierarchy.

Java Collection API hierarchy

Note that some of the most widely used implementations of the Collection API: ArrayList, LinkedList, LinkedHashMap and LinkedHashSet are now sequential collections.

More information in the JEP 431.

JEP 443 – Unnamed Patterns and Variables Preview)

This new feature of the Java language allows you to use _ as an unnamed pattern or variable. The aim is to use _ to denote a pattern or variable that is useless, the compiler will then make sure that the variable is really unused because it has no name.

Within Java’s pattern matching, _ can be used as an unnamed pattern, for example in an instanceof: instanceof Point(_, int y); or as an unnamed pattern variable, for example in an instanceof: instanceof Point(int _, int y) or in a switch: case Box(GreenBall _).

_ can also be used to denote an unnamed variable that can neither be read nor written; as it has no name, several unnamed variables can be used in the same scope.

You can use an unnamed variable:

  • As a local variable inside a statement,
  • As a resource inside a try-with-resource,
  • In a for loop header (basic or enhanced),
  • As an exception in the parameter of a catch block,
  • As a parameter of a lambda expression.

Here are a few examples from JEP 443:

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

// block statement 
Queue<Integer> 
q = ... // x1, y1, z1, x2, y2, z2, ... 
while (q.size() >= 3) {
   var x = q.remove();
   var y = q.remove();
   var _ = q.remove();
   ... new Point(x, y) ...
}

// catch block
String s = ...
try { 
    int i = Integer.parseInt(s);
    ... i ...
} catch (NumberFormatException _) { 
    System.out.println("Bad number: " + s);
}

// try-with-resources
try (var _ = ScopedContext.acquire()) {
    ... no use of acquired resource ...
}

// lamdba parameter
stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA"))

This feature, although it may seem small, is in fact a much-awaited feature that can greatly improve code readability and avoid possible bugs by clearly denoting that a variable should not be used. We can only regret that, for the moment, it is not possible to use _ as a parameter of an overloaded method.

More information in the JEP 443.

JEP 445: Unnamed Classes and Instance Main Methods (Preview)

The aim of this new feature is to make Java easier to learn and simpler to use for simple cases, such as writing a simple main method.

Let’s take a “Hello World” in Java as an example:

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

To write “Hello World” in the console, you need to know the principles of class, method, visibility and the static modifier, as well as the very specific signature of the main method in Java: the method that will be executed as the program’s entry point.

First change: enable a non-static (instance method), non-public main method with no parameters:

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

Second change: the introduction of unnamed classes:

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

An unnamed class is a class in a .class file that has no class declaration and cannot be called by another class, but can contain methods and fields. It is contained in an unnamed package.

These two new features, although targeted at developers learning Java, can greatly facilitate the writing of small programs in Java by reducing the ceremony around writing the entry point of a Java program.

Discussions around this feature also covered the case of System.out.println() (as well as reading from the console) and possible simplifications. It’s possible that a future version of Java will include simplifications in this area.

More information in the JEP 445.

Features coming out of preview

The following features comes out of preview (or incubator module) are now standard features:

  • JEP 440 – Record Patterns: enhance Java’s pattern matching with record patterns, which deconstruct a record into its attributes.
  • JEP 441 – Pattern Matching for switch: allows you to make a switch on the type of a variable (including enum, record and array), and extract a local variable of the corresponding type.
  • JEP 444 – Virtual Threads: sometimes also called green threads or lightweight threads; these are lightweight threads, with low creation and scheduling costs, which make it easier to write concurrent applications.

For details on these, please refer to my previous articles.

Features that remain in preview

The following features remain in preview (or in the incubator module).

  • JEP 442 – Foreign Function & Memory API (Third Preview): improvements following feedback from the preview. Native memory segments are now managed via a new Arena API.
  • JEP-448 – Vector API: sixth incubation of this feature. This new version includes bugfixes and performance improvements.
  • JEP 446 – Scoped Values (Preview): previously in incubation, enable the sharing of immutable data within and between threads.
  • JEP 453 – Structured Concurrency (Preview): previously in incubation, a new API to simplify the writing of multi-threaded code by allowing multiple concurrent tasks to be treated as a single processing unit.

For details on these, please refer to my previous articles.

Miscellaneous

Various additions to the JDK:

  • Character.isEmoji(), Character.isEmojiPresentation(), Character.isEmojiModifier(), Character.isEmojiModifierBase(), Character.isEmojiComponent(), Character.isExtendedPictographic().
  • Math.clamp() and StrictMath.clamp(): clamp a value between two bounds min and max.
  • StringBuilder.repeat(): joint a character or string a certain number of times.
  • HttpCLient now implements AucoCloseable and can therefore be used more easily in a try-with-resources block.
  • Locale.availableLocales(): returns the list of installed locales.
  • Collections.shuffle(List, RandomGenerator): performs random permutations of the elements of a list with a RandomGenerator.
  • String.splitWithDelimiters() and Pattern.splitWithDelimiters(): splits a string including delimiters.

All the new JDK 21 APIs can be found in The Java Version Almanac – New APIs in Java 21.

Internal changes, performance, and security

The ZGC Garbage Collector becomes generational, so it can separate the heap into several zones according to the age of the objects. To enable this feature set the command line option -XX:+ZGenerational. This Garbage Collector was created to support very large heaps (several terrabytes) with very low pauses (on the order of milliseconds). The addition of a generational heap enables it to support different workloads while consuming fewer resources. More information in the JEP 439.

G1 GC has also benefited from some new optimizations: full GCs have been optimized and the Hot Card Cache, which was proving to bring no benefit, has been removed, freeing up some native memory (0.2% of the heap size). Other changes on the Garbage Collector side can be found in this article by Thomas Schatzl: JDK 21 G1/Parallel/Serial GC changes.

On the security side, Java now supports Key Encapsulation Mechanism (KEM), an encryption technique for securing symmetric keys using public-key cryptography. More on this in the JEP 452. Leighton-Micali Signature (LMS) verification support and PBES2 cryptography algorithms have been added, you can refer to Sean Mullan’s article for an exhaustive list of the security changes included in this release: JDK 21 Security Enhancements.

The Windows 32-bit port for x86 CPUs has been deprecated for removal in a future release. Windows 10, the last version of Windows supporting 32-bit architectures, will reach end-of-life in October 2025. Depreciating and then removing the Windows 32-bit port will simplify the Open JDK build and reduce its maintenance cost. More information in the JEP 449.

Dynamic Java agent loading is now deprecated for removal. If used, it will display a WARNING in the JVM log. Java agent loading at application startup remains supported, it is only dynamic loading after application startup that is deprecated. The aim is to improve the integrity of the JVM, as an agent can modify the code of an application, loading it after JVM startup is a security risk. More information in the JEP 451.

On the performance side, Per Minborg has made improvements in the conversion between primitives (long to int, for example) via the use of VarHandle in place of existing binary calculations. As these conversion operations are widely used within Java serialization, this one takes advantage of them and sees a performance improvement of almost 5%. Other JDK APIs and many libraries also use these conversions and will see their performance improve. More information in Per Minborg’s article: Java 21: Performance Improvements Revealed.

Conclusion

Given the high number of features included in Java 21, we can expect Java 22 to be a stabilization release. I hope that some features will be out of preview in Java 22 (which should be the case for the Foreign Function & Memory API), particularly Scoped Values and Structured Concurrency, which complement Virtual Threads by making it easier to write concurrent applications.

To find all the changes in Java 21, refer to the release notes.

2 thoughts on “Java 21: what’s new ?

  1. As was pointed out on lobste.rs; Java LTS is longer than 2 years. The details vary on which JDK you’re looking at, but Oracle is going to support Java 21 until September of 2028 or 2031, depending on whether or not you’re a paying Oracle customer or not.

    Amazon announced that they’re supporting the previous LTS release, 17, for eight years until September 2029.

    A new LTS version is designated every 2 years, which is almost certainly the source of the confusion. But they’re supported for much longer than two years.

    1. Thanks for the detailed information.
      Yes, support period depends on the JDK distribution provider, so the correct sentence should be “it will be supported at least 5 years by Oracle” to keep it short.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.