Java 21: The Latest Features and Improvements

Java 21 introduces a wide array of features that enhance the language's capabilities, performance, and security. Java 21 has brought a plethora of exciting features and improvements to the table. From pattern matching and record patterns to advanced concurrency tools and cryptographic enhancements, these features provide powerful tools for developers to write more efficient and maintainable code. Whether you're working on complex concurrent applications or simply looking to improve your code's readability and performance, Java 21 offers something valuable for every developer.

In this article, we'll delve into the latest additions, explaining each feature in detail, with code examples and explanations in both technical and layman's terms.

The 15 JEPs delivered with Java 21 are grouped into six categories mapping to key long-term Java technology projects and hardware support.

1. Pattern Matching for Switch (Third Preview)

Pattern matching for switches is a new feature that allows you to specify multiple patterns in a single switch expression. This feature is still in preview mode, but it's a significant improvement over the traditional switch statement.

  1. Technical Explanation: According to the Oracle docs, "Pattern matching for switch allows you to specify multiple patterns in a single switch expression, and to deconstruct the value being switched on." (Source: Oracle Docs)
  2. Layman's Explanation: Imagine you have a variable object that can be of different types (e.g., integer, long, double, or string). With pattern matching for switches, you can specify multiple patterns in a single switch expression, making your code more concise and efficient.

Code Example

public class PatternMatchingSwitch {
    public static void main(String[] args) {
        Object obj = "Hello, Java 21!";
        
        String result = switch (obj) {
            case String s  -> "A string: " + s;
            case Integer i -> "An integer: " + i;
            case Long l    -> "A long: " + l;
            case Double d  -> "A double: " + d;
            default        -> "Unknown type";
        };       
        System.out.println(result);
    }
}

Explanation

In this example, the switch statement uses pattern matching to handle different types of objects. If obj is a String, it binds the value to s and prints it. If obj is an Integer, it binds the value to i and prints it. Otherwise, it prints "Unknown type."

2. Record Patterns (Third Preview)

Record patterns are a new way to deconstruct records in a more concise and expressive manner. Record patterns enhance pattern matching by allowing patterns to match the components of records.

  1. Technical Explanation: According to the Oracle docs, "Record patterns allow you to deconstruct records in a more concise and expressive manner, making it easier to work with records in your code." (Source: Oracle Docs)
  2. Layman's Explanation: Imagine you have a record Point with two fields, x and y. With record patterns, you can deconstruct the record in a more concise way, making it easier to access and manipulate the fields.

Code Example

public record Point(int x, int y) {}
public class RecordPatternExample {
    public static void main(String[] args) {
        Point point = new Point(3, 4);
        String result = switch (point) {
            case Point(int x, int y) when (x == y) -> "Equal coordinates";
            case Point(int x, int y) -> "Coordinates: " + x + ", " + y;
            default -> "Unknown point";
        };
        System.out.println(result);
    }
}

Explanation

Here, the switch statement uses record patterns to deconstruct the Point record and match its components. If the coordinates are equal, it prints "Equal coordinates." Otherwise, it prints the coordinates.

3. String Templates (Preview)

  1. Technical Explanation: According to the Oracle docs, "Simplifies the writing of Java programs by making it easy to express strings that include values computed at run time. Enhances the readability of expressions that mix text and expressions, whether the text fits on a single source line (as with string literals) or spans several source lines (as with text blocks)" (Source: Oracle Docs)
  2. Layman's Explanation: String templates simplify the creation of strings that include values of variables, making them easier to read and maintain.

Code Example

public class StringTemplatesExample {
    public static void main(String[] args) {
        int x = 10;
        int y = 20;
        String result = STR."Coordinates: (\{x}, \{y})";
        System.out.println(result);
    }
}

Explanation

String templates allow embedding expressions directly in strings using STR.".... The values of x and y are included in the string without the need for concatenation.

4. Sequenced Collections

  1. Technical Explanation: According to the Oracle docs, "Introduces new interfaces to represent collections with a defined encounter order. Each such collection has a well-defined first element, the second element, and so forth, up to the last element." (Source: Oracle Docs)
  2. Layman's Explanation: Java 21 introduces SequencedCollection, a new interface that extends Collection and ensures that elements have a defined encounter order. Improves developer productivity by offering a uniform set of operations that apply across a collection type that represents a sequence of elements with a defined encounter order.

Code Example

import java.util.ArrayList;
import java.util.SequencedCollection;
public class SequencedCollectionExample {
    public static void main(String[] args) {
        SequencedCollection<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");
        for (String item : list) {
            System.out.println(item);
        }
    }
}

Explanation

SequencedCollection guarantees that elements are iterated in the order they were added. This example shows how to use an ArrayList as a SequencedCollection.

5. Unnamed Patterns and Variables (Preview)

  1. Technical Explanation: According to the Oracle docs, "Enhances the Java language with unnamed patterns, which match a record component without stating the component's name or type, and unnamed variables, which can be initialized but not used. Both are denoted by an underscore character, _." (Source: Oracle Docs)
  2. Layman's Explanation: Unnamed patterns and variables allow you to ignore certain variables or patterns when you don't need them. Improves the readability of record patterns by eliding unnecessary nested patterns.

Code Example:

public class UnnamedPatternsExample {
    public static void main(String[] args) {
        var point = new Point(1, 2);
        if (point instanceof Point(int x, int _)) {
            System.out.println("X coordinate: " + x);
        }
    }
}

Explanation

The _ character is used to ignore the y coordinate in the pattern. This is useful when you only care about one part of a pattern.

6. Unnamed Classes and Instance Main Methods (Preview)

  1. Technical Explanation: According to the Oracle docs, "Offers a smooth on-ramp to Java so that educators can introduce programming concepts in a gradual manner. Reduces the ceremony of writing simple programs such as scripts and command-line utilities." (Source: Oracle Docs)
  2. Layman's Explanation: This feature is aimed at simplifying the coding experience, particularly for beginners and in educational contexts, by reducing the boilerplate code required to run simple programs.

Key Concepts

  1. Unnamed Classes: These allow you to write Java programs without explicitly naming a class. This is useful for quick scripts and educational purposes.
  2. Instance Main Methods: These provide a way to write main methods as instance methods instead of static methods, making the code more object-oriented and easier to understand for beginners.

Code Example

public class SumExample {
    void main() {
        int a = 5;
        int b = 10;
        int sum = a + b;
        System.out.println("Sum: " + sum);
    }
}

How to Run?

To run this example, use the following command.

java SumExample

This command will find and execute the instance main method.

7. Virtual Threads (Preview)

  1. Technical Explanation: According to the Oracle docs, "Virtual threads allow you to create and manage many threads without the overhead of traditional threads. Enables server applications written in the simple thread-per-request style to scale with near-optimal hardware utilization. Enables existing code that uses the java. lang.Thread API to adopt virtual threads with minimal change." (Source: Oracle Docs)
  2. Layman's Explanation: Virtual threads aim to simplify concurrent programming by providing lightweight threads that are more efficient than traditional platform threads.

Code Example

import java.util.concurrent.Executors;
public class VirtualThreadsExample {
    public static void main(String[] args) throws InterruptedException {
        var executor = Executors.newVirtualThreadPerTaskExecutor();
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                System.out.println("Hello from virtual thread: " + Thread.currentThread());
            });
        }
        executor.shutdown();
    }
}

Explanation

Virtual threads allow you to create and manage many threads without the overhead of traditional threads. This example uses a virtual thread per task executor to run 10 tasks concurrently.

8. Scoped Values (Preview)

  1. Technical Explanation: According to the Oracle docs, "Scoped values enable the sharing of immutable data within and across threads." (Source: Oracle Docs)
  2. Layman's Explanation: Scoped values provide a way to share data across multiple threads safely and efficiently.

Code Example

import jdk.incubator.concurrent.ScopedValue;
public class ScopedValuesExample {
    private static final ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();
    public static void main(String[] args) {
        ScopedValue.where(SCOPED_VALUE, "Hello, World!")
            .run(() -> {
                System.out.println(SCOPED_VALUE.get());
            });
    }
}

Explanation

Scoped values allow you to define values that are accessible within a specific scope, providing a safe way to share data across threads. This example sets a scoped value and prints it within the defined scope.

9. Foreign Function & Memory API (Third Preview)

  1. Technical Explanation: According to the Oracle docs, " Foreign Function & Memory API introduces an API by which Java programs can interoperate with code and data outside of the Java runtime. By efficiently 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. This is a preview API." (Source: Oracle Docs)
  2. Layman's Explanation: The Foreign Function & Memory API provides a way to interact with native code and memory safely and efficiently.

Code Example

import jdk.incubator.foreign.*;
public class ForeignFunctionExample {
    public static void main(String[] args) {
        try (var session = MemorySession.openConfined()) {
            MemorySegment segment = MemorySegment.allocateNative(4, session);
            segment.set(ValueLayout.JAVA_INT, 0, 42);
            int value = segment.get(ValueLayout.JAVA_INT, 0);
            System.out.println("Value: " + value);
        }
    }
}

Explanation

The Foreign Function & Memory API allows you to allocate and manage native memory. This example allocates a native memory segment, sets an integer value, and retrieves it.

10. Deprecate the Security Manager for Removal

  1. Technical Explanation: According to the Oracle docs, "The Security Manager has been deprecated for removal in a future release. This is part of a long-term effort to simplify the Java platform." (Source: Oracle Docs)
  2. Layman's Explanation: The Security Manager is an older security mechanism that is being phased out in favor of newer, more flexible, and secure alternatives like the Java Platform Module System and third-party security libraries. While there isn't a direct code example for this deprecation, developers are encouraged to explore modern security practices and tools.

11. Enhanced Pseudorandom Number Generators (PRNG)

Layman's Explanation: Java 21 enhances pseudorandom number generators with new interfaces and implementations, providing more flexible and powerful options for random number generation.

Code Example

import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
public class EnhancedPRNGExample {
    public static void main(String[] args) {
        RandomGenerator random = RandomGeneratorFactory.of("L128X128MixRandom").create();
        for (int i = 0; i < 5; i++) {
            System.out.println(random.nextInt(100));
        }
    }
}

Explanation

This example demonstrates the use of a new pseudorandom number generator, L128X128MixRandom, which provides enhanced performance and randomness properties compared to the traditional java.util.Random.

12. Structured Concurrency (Incubator)

  1. Technical Explanation: According to the Oracle docs, "Simplifies concurrent programming by introducing an API for structured concurrency. Promotes a style of concurrent programming that can eliminate common risks arising from cancellation and shutdown, such as thread leaks and cancellation delays. Improves the observability of concurrent code." (Source: Oracle Docs)
  2. Layman's Explanation: Structured concurrency simplifies concurrent programming by treating multiple tasks running in different threads as a single unit of work.

Code Example

import java.util.concurrent.StructuredTaskScope;
public class StructuredConcurrencyExample {
    public static void main(String[] args) throws InterruptedException {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            var future1 = scope.fork(() -> {
                Thread.sleep(1000);
                return "Task 1 completed";
            });
            var future2 = scope.fork(() -> {
                Thread.sleep(500);
                return "Task 2 completed";
            });
            scope.join();  // Wait for all tasks to complete
            scope.throwIfFailed();  // Propagate exceptions, if any
            System.out.println(future1.resultNow());
            System.out.println(future2.resultNow());
        }
    }
}

Explanation

Structured concurrency treats multiple asynchronous tasks as a single unit, making it easier to manage and propagate exceptions. This example runs two tasks concurrently and waits for both to complete before proceeding.

13. Vector API (Sixth Incubator)

  1. Technical Explanation: According to the Oracle docs, "Introduces 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." (Source: Oracle Docs)
  2. Layman's Explanation: The Vector API provides a way to express vector computations that are compiled to optimize vector instructions on supported hardware.

Code Example

import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorSpecies;
public class VectorAPIExample {
    private static final VectorSpecies<Integer> SPECIES = IntVector.SPECIES_256;
    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4, 5, 6, 7, 8};
        int[] b = {1, 1, 1, 1, 1, 1, 1, 1};
        int[] c = new int[a.length];
        for (int i = 0; i < a.length; i += SPECIES.length()) {
            var va = IntVector.fromArray(SPECIES, a, i);
            var vb = IntVector.fromArray(SPECIES, b, i);
            var vc = va.add(vb);
            vc.intoArray(c, i);
        }
        for (int value : c) {
            System.out.print(value + " ");
        }
    }
}

Explanation

The Vector API allows you to perform operations on vectors, which can be executed efficiently on modern hardware. This example adds two arrays using vector operations.

14. Key Encapsulation Mechanism API

  1. Technical Explanation: According to the Oracle docs, "Introduce an API for key encapsulation mechanisms (KEMs), an encryption technique for securing symmetric keys using public key cryptography." (Source: Oracle Docs)
  2. Layman's Explanation: The Key Encapsulation Mechanism (KEM) API supports cryptographic operations that combine key exchange and encryption into a single step.

Code Example

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.SecretKey;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.ECGenParameterSpec;
import javax.crypto.KeyAgreement;
public class KEMAPIExample {
    public static void main(String[] args) throws Exception {
        // Generate an ephemeral key pair for the sender
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
        kpg.initialize(new ECGenParameterSpec("secp256r1"));
        KeyPair senderKeyPair = kpg.generateKeyPair();
        // Generate the recipient's key pair
        KeyPair recipientKeyPair = kpg.generateKeyPair();
        // Perform key agreement to generate a shared secret
        KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
        keyAgreement.init(senderKeyPair.getPrivate());
        keyAgreement.doPhase(recipientKeyPair.getPublic(), true);
        byte[] sharedSecret = keyAgreement.generateSecret();
        // Derive a secret key from the shared secret
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(sharedSecret, 0, 16, "AES");
        SecretKey secretKey = skf.generateSecret(keySpec);
        System.out.println("Shared secret key: " + new String(secretKey.getEncoded()));
    }
}

Explanation

The KEM API simplifies the process of securely exchanging keys and encrypting data in a single step. This example demonstrates the generation of a shared secret key using an elliptic-curve Diffie-Hellman (ECDH) key agreement.

15. Generational ZGC

  1. Technical Explanation: According to the Oracle docs, "Improves application performance by extending the Z Garbage Collector (ZGC) to maintain separate generations for young and old objects." (Source: Oracle Docs)
  2. Layman's Explanation: Applications running with Generational ZGC should enjoy lower risks of allocation stalls, lower required heap memory overhead, and lower garbage collection CPU overhead. These benefits should come without significant throughput reduction compared to non-generational ZGC.

Conclusion

Java 21 introduces a comprehensive set of features that enhance the language's capabilities, performance, and security. From pattern matching and record patterns to advanced concurrency tools and cryptographic enhancements, these features provide powerful tools for developers to write more efficient and maintainable code. Whether you're working on complex concurrent applications or simply looking to improve your code's readability and performance, Java 21 offers something valuable for every developer.