Java 19: An Overview of the Main Features That JDK 19 Brings

The official release date for Java 19 is September 20, 2022, and while it seems rather exciting, it is important to note that since June 2022, Java 19 has been in a “Rampdown Phase One”. That means, no JPEs (JDK Enhancement Proposals) will be included in the release and there will be minor improvements and fixed bugs only. However, there are several preview and incubator features included in the JDK 19 worth paying attention to – let’s look at them in detail below.

Java 19: An Overview of the Main Features That JDK 19 Brings

New system properties

Before getting down to the feature announcements, it’s important to talk about a change that can be found in release notes only and that has not been talked about. As of Java 19, the default encoding of an operating system will be used for printing to System.out and System.err. So if you now run an existing app on Java 19, you’ll see question marks instead of special characters on the console. 

In order to change the output to UTF-8, you can either add a VM option every time when calling an app:

-Dstdout.encoding=utf8 -Dstderr.encoding=utf8

Or you can define the environment variable to globally change the settings:

_JAVA_OPTIONS="-Dstdout.encoding=utf8 -Dstderr.encoding=utf8"

Preview and incubator features in JDK 19

As stated above, JDK 19 has several preview and incubator features that the community can already test. The developers’ feedback will then be used for further work on these features and their improvement.

Structured concurrency

This feature of Java 19 aims to simplify multithread programming with the help of a structured concurrency API. If there are several tasks running in different threads, the concurrency will treat them all as a single unit of work, hence, streamlining the error handling and cancellation. 

Here are the main improvements that structured concurrency is supposed to bring:

  • In case of a canceled calling thread, subtasks are canceled too;
  • In case of an error in any subtask, other subtasks get canceled;
  • Visible call hierarchy between the calling thread and the subtask-executing threads that can be seen in the thread dump;
  • No thread pool – instead, each subtask is executed in a separate virtual thread.

Unstructured concurrency

Response handle() throws ExecutionException, InterruptedException {
    Future<String>  user  = esvc.submit(() -> findUser());
    Future<Integer> order = esvc.submit(() -> fetchOrder());
    String theUser  = user.get();   // Join findUser
    int    theOrder = order.get();  // Join fetchOrder
    return new Response(theUser, theOrder);
}

Structured concurrency

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Future<String>  user  = scope.fork(() -> findUser());
        Future<Integer> order = scope.fork(() -> fetchOrder());

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

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

Record patterns

Record patterns are intended for deconstructing record values and their main goal is to extend pattern matching for more sophisticated data queries as well as to eliminate the need to change the syntax or semantics of type patterns. As of JDK 19, record patterns and type patterns can be nested – this will allow for a declarative and composable form of data processing and navigation.

record Point(int x, int y) {}

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

Foreign function and memory API

Java 14 and Java 16 already introduced the “Foreign Memory Access API” and the “Foreign Linker API” – but these features were introduced separately and were in an incubator stage. They were combined into one feature in Java 17 but until Java 19, the “Foreign Function & Memory API” (FFM API) was still in the incubator stage. Now, with JDK 19, FFM API finally enters the preview stage.

FFM API enables interoperation with data and code outside the Java runtime and enables access to native memory directly from Java. This enhancement is supposed to improve performance, ease of use, and safety. Due to the ability to invoke foreign functions and access foreign memory in a safe manner, the API will enable Java programs to process native data and call native libraries safely and efficiently.

// 1. Find foreign function on the C library path
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixSort = linker.downcallHandle(
                             stdlib.lookup("radixsort"), ...);
// 2. Allocate on-heap memory to store four strings
String[] javaStrings   = { "mouse", "cat", "dog", "car" };
// 3. Allocate off-heap memory to store four pointers
SegmentAllocator allocator = implicitAllocator();
MemorySegment offHeap  = allocator.allocateArray(ValueLayout.ADDRESS, javaStrings.length);
// 4. Copy the strings from on-heap to off-heap
for (int i = 0; i < javaStrings.length; i++) {
    // Allocate a string off-heap, then store a pointer to it
    MemorySegment cString = allocator.allocateUtf8String(javaStrings[i]);
    offHeap.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
// 5. Sort the off-heap data by calling the foreign function
radixSort.invoke(offHeap, javaStrings.length, MemoryAddress.NULL, '\0');
// 6. Copy the (reordered) strings from off-heap to on-heap
for (int i = 0; i < javaStrings.length; i++) {
    MemoryAddress cStringPtr = offHeap.getAtIndex(ValueLayout.ADDRESS, i);
    javaStrings[i] = cStringPtr.getUtf8String(0);
}
assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"});  // true

Virtual threads

Virtual threads are a Java 19 feature that developers have anticipated for a long time and many find it to be the most exciting. These threads significantly facilitate the process of writing, maintaining, and observing high-throughput apps. Before the introduction of virtual threads, there was one Java thread corresponding to one operating system thread and since the latter one is incredibly resource-consuming, threads would often be the bottleneck in heavy-load applications.

Virtual threads are organized in a different, lightweight manner. With JDK 19, there is now a pool of carrier threads and a new virtual thread is temporarily mapped on that pool. When a virtual thread meets a blocking operation, the virtual thread is removed immediately from the carrier thread. After that, the carrier thread will execute another virtual thread and in this way, blocking operations will no longer block the executing thread.

Thread.startVirtualThread(() -> {
  // code to run in thread
});

Thread.ofVirtual().start(() -> {
  // code to run in thread
});

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -> {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return i;
        });
    });
}  // executor.close() is called implicitly, and waits

Vector API

The Vector API was first introduced in Java 16 as an incubator feature and now, Java 19 brings its fourth iteration. Vector API is a new API for mathematical vector computation and its mapping to modern Single-Instruction-Multiple-Data CPUs. As well, in its fourth iteration, this API now includes new vector operations and is able to store vectors in and read them from memory segments.
Pattern matching for switch expressions
The third preview of pattern matching for switch expressions in Java 19 extends pattern matching to switch thus allowing to test an expression against a number of patterns (where each pattern has a specific action). This helps express complex data-oriented queries in a concise and safe manner. 

Since this feature was already present in Java 17 and Java 18, naturally, its third iteration comes with improvements, such as replacing guarded patterns with when clauses in switch blocks. The development team also plans to expand the expressiveness and applicability of switch expressions in the future by enabling patterns to appear in case labels.

Java 17-18

switch (obj) {
  case String s && s.length() > 5 -> System.out.println(s.toUpperCase());
  case String s                   -> System.out.println(s.toLowerCase());

  case Integer i                  -> System.out.println(i * i);

  default -> {}
}

Java 19

switch (obj) {
  case String s when s.length() > 5 -> System.out.println(s.toUpperCase());
  case String s                     -> System.out.println(s.toLowerCase());

  case Integer i                    -> System.out.println(i * i);

  default -> {}
}

Expert Opinion

The features in Java 19 look amazing and, same as many developers, I’m particularly excited about the Virtual Threads and Structured Concurrency. I’m always looking forward to trying new things as it’s something I’ve never done before in my work – but only time will tell how well these features function and what value they truly deliver. But for now, everything looks very promising and developers behind Java seem to have done good work in terms of improving the language and listening to the community.

Senior Java Developer at SoftTeco

Denis Kuhta

Conclusion

Java is one of the oldest programming languages out there and yet, it continues to keep up with the pace of the ever-changing software development environment and please the community with new releases and improvements. It’s interesting to observe how the language progresses and it’s great to see the long-awaited features finally being brought to life in the Java 19 release.

Want to stay updated on the latest tech news?

Sign up for our monthly blog newsletter in the form below.

Softteco Logo Footer