Java 21 Features With Example

Dheeraj Singh Bhadoria
4 min readJun 26, 2023

--

Exploring the Exciting New Features in Java 21: Examples and Insights

Java 21 Features With Example

This article provides a comprehensive overview of the new features introduced in JDK 21, as specified by JSR 396 in the Java Community Process. From language improvements to performance optimizations, these features aim to enhance productivity, flexibility, and efficiency in Java development. Let’s dive into the details and explore the exciting advancements in JDK 21.

1.String Templates (Preview):
String templates, which are scheduled to be introduced as a preview feature in JDK 21, aim to simplify the process of string formatting and manipulation in Java. This feature enables developers to incorporate expressions directly within string literals, thus facilitating the creation and formatting of intricate strings. In the following blog post, we will delve into the concept of string templates, offering practical illustrations that will assist Java developers in embracing and harnessing the capabilities of this potent addition.

Let’s explore a different example to showcase the power of string templates in performing advanced formatting. Consider a scenario where you want to display a product’s information, including its name, price, and availability status. Traditionally, you might concatenate multiple strings using the + operator:

// Prior to Java 21
String productName = "Widget";
double productPrice = 29.99;
boolean productAvailable = true;

String productInfo = "Product: " + productName + "\nPrice: $" + productPrice + "\nAvailability: " + (productAvailable ? "In Stock" : "Out of Stock");

System.out.println(productInfo);

With string templates, you can simplify the formatting process and make the code more readable:

// As of Java 21
String productName = "Widget";
double productPrice = 29.99;
boolean productAvailable = true;

String productInfo = `Product: ${productName}
Price: $${productPrice}
Availability: ${productAvailable ? "In Stock" : "Out of Stock"}`;

System.out.println(productInfo);

In this example, we use string templates to embed the variables productName, productPrice, and productAvailable directly within the string literal. The expressions are enclosed within ${} and can include additional formatting, such as adding a dollar sign before the productPrice. The resulting string is more concise, easier to read, and eliminates the need for explicit concatenation and formatting operations.

2. Sequenced Collections
In JDK 21, the introduction of Sequenced Collections brings new interfaces and methods to simplify and streamline collection processing. This enhancement aims to address common scenarios where accessing the first and last elements of various collection types in Java required non-uniform and sometimes cumbersome approaches. This article explores the Sequenced Collections functionality and its benefits through examples of different collection processing scenarios.

Sequenced Collections Interfaces
Sequenced Collections introduces three new interfaces: SequencedSet, SequencedCollection, and SequencedMap. These interfaces come with additional methods that provide improved access and manipulation capabilities for collections.

Accessing the First and Last Element

Prior to JDK 21, retrieving the first and last elements of collections in Java involved different methods and approaches depending on the collection type. Let’s examine some examples of accessing the first and last elements using the pre-JDK 21 JDK API calls:

For List —
First Element — list.get(0)
Last Element — list.get(list.size()-1)

For Deque —
First Element — deque.getFirst()
Last Element — deque.getLast()

For Set —
First Element — set.iterator().next() or set.stream().findFirst().get()
Last Element — requires iterating through the set

For SortedSet —
First Element — set.first()
Last Element — set.last()

With the introduction of JDK 21 and the Sequenced Collections feature, accessing the first and last elements becomes more consistent and straightforward. The new methods simplify the process across different collection types:

For List, Deque, Set
First Element — collection.getFirst()
Last Element — collection.getLast()

3. Record Patterns
Enhance the Java programming language with record patterns to deconstruct record values. Record patterns and type patterns can be nested to enable a powerful, declarative, and composable form of data navigation and processing.

Pattern matching and records

// 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();
System.out.println(x+y);
}
}

The pattern variable p is used here solely to invoke the accessor methods x() and y(), which return the values of the components x and y. (In every record class there is a one-to-one correspondence between its accessor methods and its components.) 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. In other words:

// As of Java 21
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}

4. Pattern Matching for switch
Enhance the Java programming language with pattern matching for switch expressions and statements. Extending pattern matching to switch allows an expression to be tested against a number of patterns, each with a specific action, so that complex data-oriented queries can be expressed concisely and safely.

// 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;
}

But switch is a perfect match for pattern matching! If we extend switch statements and expressions to work on any type, and allow case labels with patterns rather than just constants, then we can rewrite the above code more clearly and reliably:

// 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();
};
}

--

--