Java 21 the new Java LTS (Long Term Support) is out and as a tribute to it I wanted to write a blog about it on the 21th day of Blogtober. What is new and which features are in it for me as a developer? I’ve taken a look at the release note from java release 18, 19, 20 and 21 and here is small summary of some new features. In this blog I only cover final released features and will exclude the features that are in preview or incubator.

Sequenced Collections

As of Java 21 they have introduced new interfaces for certain Collections that have a defined encouter order (e.g. List, Deque). The reason for this is that the different collections all have their own way of retrieving first or last elements if they have an implementation at all.

For example see the methods to get the first or last element:

  • List: list.get(0) & list.get(list.size() - 1)

  • Deque: deque.getFirst() & deque.getLast()

  • SortedSet: sortedSet.first() & sortedSet.last()

  • LinkedHashSet: Doesn’t have method to retrieve first or last element.

So they added 3 interfaces that give a uniform way to reverse and get first or last elements of the collections. This

New Sequenced Collection interfaces:

  • SequencedCollection<E>: is the superinterface of List, Deque and SequencedSet

  • SequencedSet<E>: is the superinterface of SortedSet and LinkedHashSet

  • SequencedMap<K,V>: is the superinterface of LinkedHashMap and SortedMap

SequencedCollection is also the superinterface of SequencedSet thus SequencedSet will extend both SequencedCollection<E> as Set<E>. The SequencedCollection interface exists of the following:

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

For SequencedMap<K,V> they added the following methods:

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

Record Patterns

With Java 16 we’ve had Pattern Matching for instanceof (See my previous blog about java 17) this combined with the Record type we are able to access variables directly from the target. This is best showcased by example:

Without Record Pattern:

Club utrecht = new FootballClub("FC Utrecht", 16);

if (fcutrecht instanceof FootballClub fc) {
    System.out.println("FootballClub" + fc.name() + " currently stands " + fc.rank() + " in the league");
}

Compared with the code below you can see that accessing variables of the target record directly.

With Record Pattern:

Club utrecht = new FootballClub("FC Utrecht", 16);

if (utrecht instanceof FootballClub(String name, int rank)) {
    System.out.println("FootballClub" + name + " currently stands " + rank + " in the league");
}

It’s a small feature, but I think it makes the code looks more clean.

Pattern Matching for the switch

In addition to Record Patterns it is now possible to use Pattern Matching in a switch statement. This is a powerful feature which with Record Patterns allows a developer to write clean and readable code.

Example:

Club club = new FootballClub("FC Utrecht");

switch(club) {
    case FootballClub(String name) :
        System.out.println("The club is a footballclub called: " + name);
        break;
    case BasketballClub(String name):
        System.out.println("The club is a basketballclub called: " + name);
        break;
    case HockeyClub(String name):
        System.out.println("The club is a hockeyclub called: " + name);
        break;
    default:
        System.out.println("Unknown Club type");
        break;
}

The signature of the records that implement Club doesnt have to be the same as long as they implement the interface it works.

Loom: Virtual threads

One of the bigger topics in the Java 21 release is the arrival of Virtual Threads. Before Virtual Threads there were Platform Threads (java.lang.Thread) which represented a single thread to a kernel Thread. They may be powerful but there were just so many that your hardware could handle.

In response there came Virtual Threads, a lightweight version of java.lang.Thread but instead of the kernel/OS handling the Threads the JVM abstracts it away. This way the JVM schedules the threads and will manage which one actually does some work. Under the hood the JVM still has Platform Threads, but they operate as Carriers and the JVM keeps track of which Virtual Thread is running and which one is idle/waiting. So there won’t be any Platform Threads blocked and the JVM can reuse those threads for other Virtual Threads.

Example

Because the JVM abstracts the Virtual Threads, the use of it compared to Platform Threads is quite similar.

Example of Platform Thread

Runnable runnableFn = () -> {
// your code here
};

Thread thread = new Thread(runnableFn).start();

Example of Virtual Thread

Runnable runnableFn = () -> {
  // your code here
};

Thread thread = Thread.startVirtualThread(runnableFn);

As you can see using Virtual Threads instead of Platform Threads is really easy.

Virtual Threads are still being developed further but I think it is definitely a good step forward and with Structured Concurrency (JEP 453) and Scoped Values (JEP 446) both in Preview I can’t wait to see what the future has in store!

Key Encapsulation Mechanism API

Key Encapsulation Mechanism (KEM) API is a new high-level abstraction for java to deal with secret key encryption and decryption. It gives some tools to make better use of the Key Encapsulation Mechanism technique.

What is KEM?

Key Encapsulation Mechanism is a technique to encrypt the public key to send secure keys.

With KEM the public key is encrypted by the sender and the key encapsulation message is sent to the reiever, which will decapsulate it with its own private key.

The API

Since java 21 they added the javax.crypto.KEM class.

To work with KEM you need atleast the 3 following steps:

  • Create a KeyPair with already existing java.security.KeyPairGenerator.

  • Encapsulation of a key with javax.crypto.KEM.Encapsulator

  • Decapsulation of a key with javax.crypto.KEM.Decapsulator

An example of how to use KEM:

public class Main {
    public static void main(String[] args)  {
        // Create Keypair
        var kpg = KeyPairGenerator.getInstance("X25519");
        var kp = kpg.generateKeyPair();

        // Sender encapsulates with public key
        var kem1 = KEM.getInstance("DHKEM");
        var sender = kem1.newEncapsulator(kp.getPublic());
        var encapsulated = sender.encapsulate();
        var k1 = encapsulated.key();

        // Receiver decapsulates with private key and received key encapsulation message
        var kem2 = KEM.getInstance("DHKEM");
        var receiver = kem2.newDecapsulator(kp.getPrivate());
        var k2 = receiver.decapsulate(encapsulated.encapsulation());

        assert Arrays.equals(k1.getEncoded(), k2.getEncoded());
    }
}
shadow-left