Java 19

JAVA 19

Yep, we’re another six months down the road, and it’s time for a new version of Java. Seven features (JEPs) are planned in Java SE 19.
To play around with some Java SE 19 features (without having to actually install early access), all the code in this article is executed within a docker container running OpenJDK 19 [2], see Listing:

1
2
3
4
$ docker run -it --rm \
-v $(pwd)/src:/src \
openjdk:19-slim /bin/bash
$ cd /src/java19

This article is divided into two main sections. The first section deals with new standard features. The second section discusses preview and incubator features. Normally there is a third section, where we talk about the features that are (going to be) phased out, but none have been announced for this release. For each feature, the JEP number will be listed.

NEW STANDARD FEATURES

Only one new feature has been announced in Java 19.

422: Linux/RISC-V Port.
RISC-V (pronounced “Risk-five” in English) is a RISC instruction set architecture (ISA) originally developed at the Berkley University of California. The increasing availability of RISC-V hardware makes a port of the JDK valuable.
In Java 19, this port will be complete and become part of the JDK.

PREVIEW AND INCUBATOR FEATURES

424: Foreign Function & Memory API (Preview).

This JEP replaces two previous incubation APIs: the Foreign Memory Access API (JEPs 370, 383 and 393) and Foreign Linker API (JEP 389). The earlier incubations failed. The goal of this JEP is to create and provide a more user-friendly and general-purpose API for dealing with code and data outside of JVM.

426: Vector API (Fourth Incubator).

This JEP is the fourth incubator of an API to compile vector accounts into optimal vector instructions on supported CPU architectures. This phase focuses primarily on improvements from feedback and on improved implementation and performance. This JEP builds on JEP 417 from Java SE 18, JEP414 from Java 17, and JEP 338 introduced in Java SE 16. See reference [2] for sample code.

405: Record Patterns (Preview).

Java 16 was extended with a “type pattern test” through JEP 394. In Java 17 and 18, the switch-case-statement has also been developed with it, via JEP 406 and 420, respectively; see reference [3] for code examples.

Using Type Pattern will remove the need for type-casting in most cases. However, this is only the first step toward a more declarative, data-oriented programming style. Since Java now uses records to support a more expressive way of modeling data, pattern matching can make data easier to use by enabling them to express semantic intent in their models, see next listing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JEP405 {
record Point(int x, int y) {
}

static void printSumOld(Object o) {
if (o instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x + y);
}
}

static void printSumNew(Object o) {
if (o instanceof Point(int x,int y)) {
System.out.println(x + y);
}
}

public static void main(String[] args) {
printSumOld(new Point(22, 20));
printSumNew(new Point(20, 22));

}
}
1
2
3
$ java --enable-preview --source 19 JEP405.java
42
42

427: Pattern Matching for switch (Third Preview)

This is the third preview of pattern matching for switch statements that was first released in Java 17 in JEP 406 and its second preview received in Java 18 in JEP 420. In this third preview, mainly minor improvements have been made based on user feedback and user experience. Check out the sample code[2] and the Java 17 article [3].

425: Virtual Threads (Preview)

Virtual Threads are part of project Loom [4]. Project Loom is aimed at improving concurrency performance in Java by letting the developer write concurrency applications with known APIs to write, maintain and use hardware resources more efficiently.

Virtual Threads are new, lightweight implementations of Java’s thread class that are scheduled by the JDK, rather than by the operating system (OS), as has been the case so far in Java. Sample code is omitted here because you can read a whole article about it in the next Java Magazine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static java.time.Duration.ofSeconds;

public class JEP425 {

private static Runnable sleepyHead(AtomicInteger atomicInteger) {
return () -> {
try {
Thread.sleep(ofSeconds(1).toMillis());
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("Work Done - " + atomicInteger.incrementAndGet());
};
}

public static void main(String[] args) throws InterruptedException {
final AtomicInteger atomicInteger = new AtomicInteger();
Instant start = Instant.now();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
executor.submit(sleepyHead(atomicInteger));
}
}
Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();
System.out.println("Total elapsed time : " + timeElapsed / 1000.0 + " seconds");
}
}
1
2
3
4
5
6
7
8
$ java --enable-preview --source 19 JEP425.java
[...]
Work done - 9996
Work done - 9997
Work done - 9998
Work done - 9999
Work done - 9989
Total elapsed time : 1.471 seconds

428: Structured Concurrency (Incubator)

The idea behind Structured Concurrency is to make the lifetime of one or more threads work the same as a code block in structured programming. Structured Concurrency treats multiple tasks in different threads as a single unit of work, which streamlines error handling, which improves reliability and observability (debugging).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import jdk.incubator.concurrent.*;

import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;

public class JEP428 {

String fooSequential() throws InterruptedException, IOException {
String bar = bar(); // kan een Exception opleveren
String baz = baz(); //ditto
return baz + bar;
}

String fooStructured() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> bar = scope.fork(this::bar);
Future<String> baz = scope.fork(this::baz);

scope.join();
scope.throwIfFailed();

return bar.resultNow() + baz.resultNow();
}
}

String fooThreaded() throws ExecutionException, InterruptedException {
var executorService = new ForkJoinPool(2);
Future<String> bar = executorService.submit(this::bar);
Future<String> baz = executorService.submit(this::baz);
try {
return bar.get() + baz.get();
} finally {
executorService.shutdown();
}
}

String bar() throws InterruptedException {
Thread.sleep(2000);
return "bar";
}

String baz() throws IOException, InterruptedException {
Thread.sleep(500);
return "baz";
// throw new IOException("baz");
}

public static void main(String[] args) {
var self = new JEP428();
long start = System.currentTimeMillis();
try {
System.out.println(self.fooSequential());
} catch (InterruptedException | IOException e) {
System.out.println("Interrupted in sequential");
}
long end = System.currentTimeMillis();
System.out.println("Sequential took " + (end - start) + " ms");

start = System.currentTimeMillis();
try {
System.out.println(self.fooThreaded());
} catch (ExecutionException | InterruptedException e) {
System.out.println("Interrupted in threaded");
}
end = System.currentTimeMillis();
System.out.println("Threaded took " + (end - start) + " ms");

start = System.currentTimeMillis();
try {
System.out.println(self.fooStructured());
} catch (ExecutionException | InterruptedException e) {
System.out.println("Interrupted in structured");
}
end = System.currentTimeMillis();
System.out.println("Structured took " + (end - start) + " ms");
}
}

The listing above gives three examples of a ‘foo-method’. One where it’s called in a multithreaded method, and one where Structured Concurrency is applied. In the fooSequential-
method, it is abundantly clear to the average developer what happens if an exception occurs in one of the statements. FooSequential will fail on that statement.

How fooThreaded fails if, for example, in baz() an exception is thrown is a lot harder to understand. The threads must, in fact, completely resolve before the foo-method will propagate the error. Namely, it will first evaluate bar() and it will only return after two seconds. This is because the baz and bar calls run in isolation. It goes even further. Suppose that this foo-method itself fails before the joining calls are made. Then foo will already fail, but the threads will just continue.

In the fooStructured-method, the created threads are seen as one unit of work, and the foo method will immediately return if any of the other calls fail.

In Listing below, where the code is run without an exception being thrown, you can see that the Sequential call takes more than 2500ms because bar and baz are called one after the other.

1
2
3
4
5
6
7
8
9
10
$ java --enable-preview --source 19 --add-modules jdk.incubator.concurrent JEP428.java
WARNING: Using incubator modules: jdk.incubator.concurrent
warning: using incubating module(s): jdk.incubator.concurrent
1 warning
bazbar
Sequential took 2504 ms
barbaz
Threaded took 2008 ms
barbaz
Structured took 2062 ms

There is not much difference between Threaded and Structured. However, the big difference becomes obvious as soon as something does go wrong (Listing below). Then you can see that when using Structured Concurrency, the foo method fails, as soon as the first exception is thrown (in the baz method).

1
2
3
4
5
6
7
8
$ java —source 19 —enable-preview \
—add-modules jdk.incubator.concurrent JEP428.java
Interrupted in sequential
Sequential took 2512 ms
Interrupted in threaded
Threaded took 2019 ms
Interrupted in structured
Structured took 531 ms

CONCLUSION

Lots of incubators and preview features in this version of Java, but also some really high potential features with a lot of promise. If the Virtual Threads and Structured Concurrency make it to the core of Java, then that bodes well. Multi threaded work as if you were just doing structured programming. I’m in favor!

References:

  1. https://openjdk.org/projects/jdk/19/
  2. http://ivo2u.nl/Vy
  3. http://ivo2u.nl/oz
  4. https://openjdk.org/projects/loom/