logo

Go back to Blogs

Exploring New Features in Java 21

February 25, 2025
ℹ️
  • We sometimes use affiliate links in our content. This means that if you click on a link and make a purchase, we may receive a small commission at no extra cost to you. This helps us keep creating valuable content for you!

Content

Introduction

Java 21 brings a host of new features that significantly enhance the language’s power and expressiveness. Over the years, Java has evolved from a simple, object-oriented programming language to a robust platform that supports modern development practices and paradigms. In this article, we will delve into some of the most exciting additions in Java 21, such as pattern matching for switch, record patterns, virtual threads, sequenced collections, string templates, and the Generational Z Garbage Collector (ZGC).

We will compare these features with their counterparts in previous versions of Java, highlighting the improvements and new capabilities introduced in Java 21. This exploration will provide a comprehensive understanding of how Java has evolved to meet the needs of contemporary software development.

1. Pattern Matching for switch (Third Preview)

Pattern matching for ‘switch’ allows you to write more concise and readable code when working with different types. This feature simplifies the process of checking an object’s type and extracting its value, making your code more expressive and easier to maintain. By enabling more readable and maintainable code, pattern matching for ‘switch’ helps reduce boilerplate code and potential errors, enhancing overall code quality and developer productivity.

Java 21 Code Example

public String formatObjectJdk21(Object obj) {
   return switch (obj) {
       case Integer i -> String.format("Integer: %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);
       case int[] Objects -> {
           yield "Object: " + Arrays.toString(Objects);
       }
       default -> obj.toString();
   };
}

Previous Versions (Java 11)

In Java 11, you would typically use a series of if-else statements or a traditional switch statement without pattern matching.

public String formatObject(Object obj) {
   if (obj instanceof Integer) {
       return String.format("Integer: %d", (Integer) obj);
   } else if (obj instanceof Long) {
       return String.format("Long: %d", (Long) obj);
   } else if (obj instanceof Double) {
       return String.format("Double: %f", (Double) obj);
   } else if (obj instanceof String) {
       return String.format("String: %s", (String) obj);
   } if (obj instanceof int[]) {
       return "Object: " + Arrays.toString((int[]) obj);
   } else if (obj instanceof long[]) {
       return "Object: " + Arrays.toString((long[]) obj);
   } else if (obj instanceof double[]) {
       return "Object: " + Arrays.toString((double[]) obj);
   } else if (obj instanceof char[]) {
       return "Object: " + Arrays.toString((char[]) obj);
   } else if (obj instanceof float[]) {
       return "Object: " + Arrays.toString((float[]) obj);
   } else if (obj.getClass().isArray()) {
       return "Object: " + Arrays.deepToString((Object[]) obj);
   } else {
       return "Object: " + obj;
   }
}

2. Record Patterns (Second Preview)

Record patterns allow you to deconstruct record values in a more readable and concise way. This feature simplifies working with records by providing a clear and straightforward syntax for extracting their components. By using record patterns, developers can write more maintainable and expressive code, reducing boilerplate and potential errors. This enhancement is particularly useful for data classes and other structures where immutability and pattern matching are common, making it easier to handle complex data transformations and manipulations.

Java 21 Code Example

public record PointJdk21(int x, int y) {}


public class RecordPatternJdk21 {
   public String describePointJdk21(PointJdk21 point) {
       return switch (point) {
           case PointJdk21(int x, int y) -> String.format("PointJdk21(%d, %d)", x, y);
       };
   }
}

Previous Versions (Java 11)

Records were introduced in Java 14 as a preview feature and became a standard feature in Java 16. In Java 11, you would use traditional classes.

public class Point {
   private final int x;
   private final int y;
   public Point(int x, int y) {
       this.x = x;
       this.y = y;
   }
   public int getX() {
       return x;
   }
   public int getY() {
       return y;
   }
}

public class RecordPattern {
   public String describePoint(Point point) {
       return String.format("Point(%d, %d)", point.getX(), point.getY());
   }
}

3. Virtual Threads (Preview)

Virtual threads are lightweight threads introduced in Java 21 that can significantly improve the scalability of concurrent applications. Unlike traditional threads, virtual threads are managed by the Java runtime rather than the operating system, allowing you to create and manage a large number of threads with minimal overhead. This makes it easier to write highly concurrent applications that can handle many tasks simultaneously without the performance penalties typically associated with traditional threading models. Virtual threads are particularly useful for I/O-bound applications, where the ability to handle many concurrent operations can lead to substantial performance improvements.

Java 21 Code Example

public class VirtualThreadJdk21 {


   public void runVirtualThread() {
       try {
           Thread vThread = Thread.ofVirtual().start(() -> {
               System.out.println("Hello from virtual thread!");
           });
           vThread.join();
       } catch (InterruptedException e) {
           System.out.println(e.getMessage());
       }
   }
}

Previous Versions (Java 11)

In Java 11, you would use traditional threads.

public class TraditionalThreadExample {
   public void runThread() {
       try {
           Thread thread = new Thread(() -> {
               System.out.println("Hello from traditional thread!");
           });
           thread.start();
           thread.join();
       } catch (InterruptedException e) {
           System.out.println(e.getMessage());
       }
   }
}

4. Sequenced Collections

Java 21 introduces sequenced collections, which provide a unified way to handle ordered collections. This feature simplifies the manipulation of collections that maintain a specific order, such as lists and sets. Sequenced collections ensure that elements are processed in a predictable sequence, making it easier to perform operations like iteration, sorting, and filtering. By providing a consistent API for ordered collections, Java 21 enhances code readability and maintainability, allowing developers to write more intuitive and efficient code.

Java 21 Code Example

import java.util.ArrayList;
import java.util.SequencedCollection;


public class SequencedCollectionsJdk21 {


   private final SequencedCollection<String> sequencedCollection;


   public SequencedCollectionsJdk21() {
       this.sequencedCollection = new ArrayList<>();
   }


   public void addElements() {
       sequencedCollection.add("first");
       sequencedCollection.add("second");
       sequencedCollection.add("third");
   }
   public SequencedCollection<String> getSequencedCollection() {
       return sequencedCollection;
   }
}

Previous Versions (Java 11)

In Java 11, you would use traditional collections like ArrayList or LinkedList without the unified interface for sequenced collections.

import java.util.ArrayList;
import java.util.List;


public class TraditionalCollectionsExample {
  
   private final List<String> list;
  
   public TraditionalCollectionsExample() {
       this.list = new ArrayList<>();
   }


   public void addElements() {
       list.add("first");
       list.add("second");
       list.add("third");
   }
   public List<String> getList() {
       return list;
   }
}

5. String Templates (Preview)

String templates in Java 21 simplify the creation of strings that include expressions, making it easier to embed variables and expressions directly within string literals. This feature enhances code readability and reduces the likelihood of errors associated with manual string concatenation. String templates allow developers to write more concise and maintainable code, especially when dealing with complex string manipulations. This is a preview language feature and API.

import static java.lang.StringTemplate.STR;


public class StringTemplateJdk21 {
   public String createTemplateString(int x, int y) {
       String name = "Alice";
       int age = 30;
       String message = STR."Hello, my name is \{name} and I am \{age} years old.";
       System.out.println(message);
   }
}

Previous Versions

Since string templates are preview language feature and API and are not supported in Java 21 main streamline, you can use traditional String.format or concatenation in java 21.

public class StringTraditional {


   public static void workWithString() {
       String name = "Alice";
       int age = 30;


       // Using String.format
       String messageFormat = String.format("Hello, my name is %s and I am %d years old.", name, age);
       System.out.println(messageFormat);


       // Using concatenation
       String messageConcat = "Hello, my name is " + name + " and I am " + age + " years old.";
       System.out.println(messageConcat);
   }
}

6. Generational ZGC (Z Garbage Collector)

Generational ZGC is a new feature introduced in Java 21 that enhances the performance of the ZGC by adding generational capabilities. This means that the ZGC can now manage objects in different generations (young and old), which can lead to more efficient garbage collection and improved application performance.

Comparison with Previous ZGC

The original ZGC, introduced in Java 11, was designed to handle large heaps with low latency. It was a single-generation garbage collector, meaning it treated all objects the same regardless of their age. While this approach was effective for many applications, it did not take advantage of the generational hypothesis, which states that most objects die young.

Generational ZGC in Java 21 builds on the original ZGC by introducing separate regions for young and old objects. This allows the garbage collector to focus on collecting young objects more frequently, which are more likely to be short-lived, while collecting old objects less frequently. This generational approach can lead to better performance and reduced pause times, especially in applications with a high allocation rate. With JDK 21 to use Generational ZGC requires the following two JVM arguments:

$java -XX:+UseZGC -XX:+ZGenerational

Conclusion

Java 21 introduces several powerful features that enhance the language’s expressiveness and performance. Pattern matching for switch, record patterns, virtual threads, sequenced collections, string templates, and Generational Z Garbage Collector are just a few of the exciting additions. These features allow developers to write more concise, readable, and efficient Java applications, making the language more robust and versatile for modern software development.

By incorporating these features into your code, you can significantly improve code maintainability and performance. Pattern matching for switch simplifies type checks and value extraction, while record patterns provide a more readable way to deconstruct records. Virtual threads offer a lightweight threading model that enhances concurrency, and sequenced collections ensure predictable order in collections. String templates streamline the creation of strings with embedded expressions, reducing boilerplate code.

Be sure to explore the official Java 21 documentation for more details and additional features.

References

Footer