Contents

What's new in Java 21?

An overview of language and libbrary changes

Java 21

This is a real thing! Java 21 feels almost as a different programming language! Changes planned to be released in Sptember 2023 (in just a month!) can be roghly grouped into two categories:

Language and library changes

Here are the most important and - in my opinion - the most interesting changes that await us in Java 21. They are not revolutionary, but they make writing code - and, what is even more important, reading it - much easier.

Other changes

These are low-level changes, important for library creators (vector API, FFI, cryptography), or related to "code clean-up." They are probably less interesting for the typical programmer, although it's worth taking a look at the descriptions in JEPs to have a general idea of what they are about.

Piece by piece

Sequenced collections

We will have new interfaces which would be able to correctly model the encounter order. The API we have so far (let me say this: all Collection API) is a bit chaotic: taking first or last element from different collections has different API:

Data Structure First element Last element
List list.get(0) list.get(list.size() - 1)
Deque deque.getFirst() deque.getLast()
SortedSet ss.first() ss.last()
LinkedHashSet lhs.iterator().next() // missing

The two new interfaces are: SequencedCollection<E>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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();
}

and SequencedMap<K,V>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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

Pattern matching for Java… You could finally write something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// As of Java 21
record MyPair<S,T>(S fst, T snd){};

static void recordInference(MyPair<String, Integer> pair){
    switch (pair) {
        case MyPair(var f, var s) -> 
            ... // Inferred record pattern MyPair<String,Integer>(var f, var s)
        ...
    }
}

Patterns in ‘switch’

Expression that chooses the branch does not have to be of Enum type, and in case branches one can use qualified enumeration values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// As of Java 21
sealed interface CardClassification permits Suit, Tarot {}
public enum Suit implements CardClassification { CLUBS, DIAMONDS, HEARTS, SPADES }
final class Tarot implements CardClassification {}

static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) {
    switch (c) {
        case Suit.CLUBS -> {
            System.out.println("It's clubs");
        }
        case Suit.DIAMONDS -> {
            System.out.println("It's diamonds");
        }
        case Suit.HEARTS -> {
            System.out.println("It's hearts");
        }
        case Suit.SPADES -> {
            System.out.println("It's spades");
        }
        case Tarot t -> {
            System.out.println("It's a tarot");
        }
    }
}

Virtual threads

How to create them?

  • by submitting tasks to a new executor Executors.newVirtualThreadPerTaskExecutor() (non-pooling, running each task in virtual thread, uses special internal fork-join pool)
  • new Thread.Builder class (early access JavaDoc API)
  • can be created using Thread.startVirtualThread(Runnable) (javadoc)
  • … or by using: Thread.ofVirtual()

What features they have?

  • are always daemon threads (Thread.setDaemon(false) is just noop)
  • have unchangable priority
  • canno be assigned to thred groups
  • public constructor always creates platform threads

Additionally

  • Thread.isVirtual() - you can check what thread are you on
  • Thread.getAllStackTraces() returns a map of platform threads, not all threads

Text patterns

Here we’ll have much more than string interpolation in other languages. And that’s good becasue we’re so late to the party…

Nice table from JEP 430:

Language Code
C# $"{x} plus {y} equals {x + y}"
Visual Basic $"{x} plus {y} equals {x + y}"
Python f"{x} plus {y} equals {x + y}"
Scala s"$x plus $y equals ${x + y}"
Groovy "$x plus $y equals ${x + y}"
Kotlin "$x plus $y equals ${x + y}"
JavaScript `${x} plus ${y} equals ${x + y}`
Ruby "#{x} plus #{y} equals #{x + y}"
Swift "\(x) plus \(y) equals \(x + y)"

In Java we’ll be able to write:

1
2
3
4
5
6
7
// Embedded expressions can be strings
String firstName = "Bill";
String lastName  = "Duck";
String fullName  = STR."\{firstName} \{lastName}";
| "Bill Duck"
String sortName  = STR."\{lastName}, \{firstName}";
| "Duck, Bill"

or (html):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// html
tring title = "My Web Page";
String text  = "Hello, world";
String html = STR."""
        <html>
          <head>
            <title>\{title}</title>
          </head>
          <body>
            <p>\{text}</p>
          </body>
        </html>
        """;

or (json):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// json
String name    = "Joan Smith";
String phone   = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = STR."""
    {
        "name":    "\{name}",
        "phone":   "\{phone}",
        "address": "\{address}"
    }
    """;

Structured concurrency

Main class of interest is StructuredTaskScope StructuredTaskScope. It allows to give a structure to the complex task which consists of many dependent, concurrent subtasks (function fork()) and coordinate them: join() is for whole group of subtasks forked from main task; we can use new APIjoinUttil(java.time.Instant) and we can cancel (function shutdown()) all tasks at once.

Pros: better error handling, thread cancellation propagation (but still no explicit context), readable code structure, readable thread structure in thread dumps:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier<String>  user  = scope.fork(() -> findUser());
        Supplier<Integer> order = scope.fork(() -> fetchOrder());

        scope.join()            // Join both subtasks
             .throwIfFailed();  // ... and propagate errors

        // Here, both subtasks have succeeded, so compose their results
        return new Response(user.get(), order.get());
    }
}

Patterns and unnamed values

Where we don’t need a value, we don’t use it:

In switch:

1
2
3
4
    case Box(RedBall _), Box(BlueBall _) -> processBox(b);
    case Box(GreenBall _)                -> stopProcessing();
    case Box(_)                          -> pickAnotherBox();
}

In for:

1
2
3
4
5
6
int acc = 0;
for (Order _ : orders) {
    if (acc < LIMIT) { 
        ... acc++ ...
    }
}

…or in catch blocks:

1
2
3
4
5
6
7
String s = ...
try { 
    int i = Integer.parseInt(s);
    ... i ...
} catch (NumberFormatException _) { 
    System.out.println("Bad number: " + s);
}

Unnamed classess

… are a simplification in writing ‘hello, world’. It would be sufficient for the compiler to compile “an unnamed class” when it encounters this code: taki kod:

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

A plan

In the next entries, I will check in more detail what these changes concern, describe them and I will use them in simple programs. Maybe I can do it before the September release?

Happy coding!

Pictures

Pictures genenrated w https://creator.nightcafe.studio/