Discover Azul's high-performance Java platform providing faster speed, startup, & efficiency without code changes
Blog chevron_right Java

JDK 22, an Average Release (Sort of)

With atomic clock-like regularity, the next version of Java, JDK 22, was released today. Although this is not a long-term support (LTS) release, there is nothing to stop you from using it in production, and it contains some interesting new features.

Let’s delve in and see what this brings us.

New features for the Java platform are defined through JDK Enhancement Proposals (JEPs), and JDK 22 contains 12 of these. Coincidentally, over the last 13 Java releases, since the switch to a six-month cadence, the average number of JEPs (to the nearest integer) is also 12. You could, therefore, describe this as an average release!

Learn More About JDK 22

Event

JDK 22: Twice as Good as JDK 11

March 27th at 10am PT | Virtual
Research & White Papers

Azul State of Java Survey and Report 2023

Breaking this down further, we have four final features and eight preview features or incubator modules.

Java language changes

JEP 447: Statements before super() [Preview]. As an object-oriented language, Java provides the ability to extend another (non-final) class and inherit both state and behavior (where permitted). To make this work reliably, the constructors of classes must be called in top-down order to avoid subclass constructors interfering with superclass ones. When a developer decides to make an explicit call to the superclass constructor, this must be the first statement.

This imposes some limitations, which can lead to more complex code than is desired. The most obvious of these is the inability to perform a test on constructor parameters before calling the superclass constructor. If a test results in an exception being thrown, the call to the superclass constructor becomes a technically unnecessary overhead.

This JEP introduces a controlled way to allow statements before a call to super(). Any reference to the this or super object references will generate errors as the object being referenced would not have been fully initialized. There are several other restrictions described in detail in the JEP.

JEP 456: Unnamed Patterns and Variables. Introduced as a preview feature in JDK 21, this is now final without any changes.

This feature allows developers to use a single underscore _ to represent a variable that will not be used. Rather than having named variables that clutter our code and make it harder to see what is going on, we can maintain the correct syntax for a construct and aid readability.

One simple example is a foreach style loop that iterates over a collection but does not use the value retrieved:

            for (String name : userList)
                userCount++;

This can be simplified to:

            for (String _ : userList)
                userCount++;

This is a rather trivial example, but when applied to patterns, its value becomes clearer. Using composed record patterns, which are a form of deconstruction pattern, we might have something like this:

            public void printTopLeftX(Rectangle r) {
                 if (r instanceof ColourRectangle(ColourPoint(
                       Point(double x, double y), Colour c),
                       ColourPoint bottomRight))
                 System.out.println(x);
                 }

Since all we are using is the value of x, all the other variables (including their type definition) are superfluous and make the code very dense.  Using unnamed patterns, we can rewrite this as:

            public void printTopLeftX(Rectangle r) {
                 if (r instanceof ColourRectangle(ColourPoint(Point(var x, _), _), _) )
            System.out.println(x);
            }

These can also be used in pattern matching for switch and lambda expressions.

JEP 459: String Templates (Preview). Introduced as a preview feature in JDK 21, this is still in preview with no developer-visible changes.

Building strings in Java can be accomplished in several different ways: through string concatenation and the use of library classes like StringBuilder and String.format(). String templates provide a simplified way of expressing strings that contain values that are computed at runtime.

We now have a string template processor, STR, that can perform string interpolation.

As a simple example, we can insert values:

            String name = "Simon";
                 String info = STR." My name is \{name}";

The real power of string templates is that you can do much more sophisticated interpolation, for example, using method calls:

            String s = STR." We are here \{
                      // Method call
                 DateTimeFormatter
                           .ofPattern("B")
                           .format(LocalTime.now())
                 } learning Java";

Notice that we can indent code as we want without affecting the generated string and even include comments.

JEP 463: Implicitly Declared Classes and Instance Main Methods [Preview]. This was introduced as a preview feature in JDK 21 and continues that way with some significant changes.

To write even the simplest Java application requires a lot of ceremony (or boiler-plate code). For the customary “hello world,” we need all of this:

             public class Hello {

                  public static void main(String[] args) {

                       System.out.println(“Hello, World!”);

                  }

             }

This JEP allows a program to use an instance main method, which does not need to be marked as static or public. It also provides a compilation unit such that we no longer explicitly need to define the class.  The overall effect of these changes is to reduce our application to:

             void main(){

                  System.out.printIn("Hello, World!");

             }

This will be of limited use to experienced developers but will help those getting started with Java.

Library Changes

JEP 454: Foreign Function & Memory API. Introduced initially as a preview feature in JDK 19, this is now a final feature.

This API is part of Project Panama, and it provides a simpler replacement for the Java Native Interface (JNI). These APIs provide standard ways to interact with native memory and external libraries, which are compiled directly into native code rather than bytecodes. Bi-directional interaction is possible through down-calls (from Java to native functions) and up-calls (callbacks from native code to Java code).

An additional tool, jextract, can be used to generate code to access an external library. This is not included by default in the JDK but is available as source code from OpenJDK.

JEP 457: Class-File API [Preview]. For most developers, this will not be an interesting API. Processing class files is something that the JVM obviously must do (to load and run applications) and is also something required by many frameworks and libraries.

The goal of this API is to standardize and simplify how users can interact with class files.

JEP 460: Vector API [Preview]. This API holds the record as the longest preview feature, being in its seventh iteration in JDK 22.

Vectors, in this case, refer to very wide registers available in all modern CPUs. Using a technique called single instruction, multiple data (SIMD), multiple elements from an array can be loaded into the vector and the same operation (such as adding or subtracting a value) can be performed in a single clock cycle. This parallel processing can lead to significant performance improvements for numerically intensive operations.

The API provides ways for the developer to specify how the values should be stored in the vectors and the operations that are required on them.

For simple situations, the JVM’s internal just-in-time (JIT) compiler will recognize where vector operations can be used and apply them automatically. For more complex situations, for example, where a loop includes a conditional test for a calculation, this API will be helpful.

Alternatively, the Azul Platform Prime Zing JVM uses a replacement JIT compiler called Falcon. Based on the open-source LLVM project, this can recognize many more situations where vector operations can be used and deliver better performance without code modifications.

JEP 461: Stream Gatherers [Preview]. The streams API was introduced in JDK 8 and, combined with lambda expressions, provided a more functional style of programming that had not been possible in Java before.

Streams consist of a source which provides elements that can be passed through zero or more intermediate operations before a result or side effect is generated using a terminal operation.

For the terminal operation, it is possible to define your own functionality through Stream::collector.  Although there is a rich set of intermediate operations, these are fixed and cannot be extended by a developer.

The Stream gatherer API now provides a way to define a new intermediate operation.

A gatherer is defined by four functions: an optional initializer, an integrator, an optional combiner, and an optional finisher. Using these, developers have complete flexibility over how input elements to the gatherer are processed and the output that is generated to be passed to either the next intermediate or terminal operation.

JEP 462: Structured Concurrency [Preview]. This was first introduced in JDK 19 as an incubator module and has been a preview feature since JDK 21. No changes have been made in this release.

By its very nature, writing reliable multithreaded code is challenging. Over the history of Java, we have had many new features aimed at making this task at least somewhat more straightforward. The concurrency utilities, the fork-join framework, and parallel streams have all provided solutions to different types of multi-threading situations.

Structured concurrency, part of Project Loom, treats multiple tasks running in different threads as a single unit of work. This makes both error handling and cancellation simpler to deal with.

JEP 464: Scoped Values [Preview]. This was introduced as a preview feature in JDK 21 and continues in JDK 22 without change.

Scoped values are also related to Project Loom and provide an alternative to thread-local variables. The critical difference is that, as a value rather than a variable, they are immutable.

Thread-local variables have more complexity than is typically needed and have a substantial resource cost associated with them. With virtual threads allowing scalability to orders of magnitude greater than with platform threads, this resource cost can become a limiting factor. Scoped values resolve this issue in a way that allows developers the flexibility they need for multithreaded application logic.

JVM and Other Changes

JEP 423: Region Pinning for G1. This is an improvement to the way the G1 garbage collector works that reduces latency when using JNI.

Unlike Java code, JNI interactions may use explicit pointers to objects in the heap. Relocating those objects within the heap is not possible when a Java thread is executing a critical region of JNI code. To avoid this problem, G1 disables GC during critical regions, which can lead to increased latency, as other non-JNI threads will be blocked if they trigger a GC.

This change allows G1 GC to run even when a thread is in a critical region by pinning memory regions used by the JNI code but allowing others to be relocated and collected.

JEP 458: Launch Multi-File Source-Code Programs. In JDK 11, JEP 330 introduced the ability to run a single source file without the need to compile it directly. This is related to simplifying getting started with Java, as discussed in JEP 463 above.

This JEP enhances the java application launcher by enabling it to run a program supplied as multiple files of Java source code rather than just one. Most developers use an IDE for project development and jshell for code snippet experimentation. 

This feature will be of most use to those just starting their journey of learning to program in Java.

Conclusions

As you can see, JDK 22 contains an average number of new features but is better than average in what those features deliver. Azul has free downloads of our Zulu builds of OpenJDK Community Edition that you can install and experiment with. Why not give it a try?

Free Downloads

Try Zulu Builds of OpenJDK Community Edition