1. Introduction

In this tutorial, we’ll dive deep into the latest Java release, Java 22, which is now in General Availability.

2. Java Language Updates

Let’s talk about all the new changes to the Java language as part of this release.

2.1. Unnamed Variables and Patterns – JEP 456

We often define temporary variables or pattern variables that remain unused in code. More often than not, it is due to language constraints, and removing them is prohibited or introduces side effects. Exceptions, switch patterns, and Lambda expressions are examples where we define variables or patterns in a certain scope, but we never get to use them:

try {
    int number = someNumber / 0;
} catch (ArithmeticException exception) {
    System.err.println("Division by zero");
}

switch (obj) {
    case Integer i -> System.out.println("Is an integer");
    case Float f -> System.out.println("Is a float");
    case String s -> System.out.println("Is a String");
    default -> System.out.println("Default");
}

try (Connection connection = DriverManager.getConnection(url, user, pwd)) {
    LOGGER.info(STR."""
      DB Connection successful
      URL = \{url}
      usr = \{user}
      pwd = \{pwd}""");
} catch (SQLException e) {}

Unnamed variables(_) are perfect in such scenarios and make the variable’s intention unambiguous. They cannot be passed in code or used or assigned values. Let’s rewrite the previous examples:

try {
    int number = someNumber / 0;
} catch (ArithmeticException _) {
    System.err.println("Division by zero");
}

switch (obj) {
    case Integer _ -> System.out.println("Is an integer");
    case Float _ -> System.out.println("Is a float");
    case String _ -> System.out.println("Is a String");
    default -> System.out.println("Default");
}

try (Connection _ = DriverManager.getConnection(url, user, pwd)) {
    LOGGER.info(STR."""
      DB Connection successful
      URL = \{url}
      usr = \{user}
      pwd = \{pwd}""");
} catch (SQLException e) {
    LOGGER.warning("Exception");
}

2.2. Statements before super() – JEP 447

Java, for a long time, didn’t allow us to put any statements before calling super() inside the constructor of a child class. Let’s say we have a class system of Shape and two classes, Square and Circle, extending from the Shape class. In the child class constructors, the first statement is a call to super():

public class Square extends Shape {
    int sides;
    int length;

    Square(int sides, int length) {
        super(sides, length);
        // some other code
    }
}

This was inconvenient when we would benefit from performing certain validations before even calling super(). With this release, this is addressed:

public class Square extends Shape {
    int sides;
    int length;

    Square(int sides, int length) {
        if (sides != 4 && length <= 0) {
            throw new IllegalArgumentException("Cannot form Square");
        }
        super(sides, length);
    }
}

We should note that statements we put before super() cannot access instance variables or execute methods. We can use this to perform validations. Additionally, we can use this to transform values received in the derived class before calling the base class constructor.

This is a preview Java feature.

3. String Templates – JEP 459

Java 22 introduces the second preview to Java’s popular String templates feature. String templates allow the embedding of literal texts along with expressions and template processors to produce specialized results. They are also a much safer and more efficient alternative to other String composition techniques.

With this release, String Templates continue to be in preview, with a minor update to the API since the first preview. A new change is introduced to the typing of template expressions to use the return type of the corresponding process() method in template processors.

4. Implicitly Declared Classes and Instance Main Methods – JEP 463

Java finally supports writing a program without defining an explicit class or a main method with its standard template. This is how we generally define a class:

class MyClass {
    public static void main(String[] args) {
    }
}

Developers can now simply create a new file with a main() method definition as follows and start coding:

void main() {
    System.out.println("This is an implicitly declared class without any constructs");

    int x = 165;
    int y = 100;

    System.out.println(y + x);
}

We can compile this using its file name. The unnamed classes reside in an unnamed package, which resides in an unnamed module.

5. Libraries

Java 22 also brings new libraries and updates some existing ones.

5.1. Foreign Function and Memory API – JEP 454

Java 22 finalized the Foreign Function and Memory API after a few incubator iterations as part of Project Loom. This API allows developers to invoke foreign functions, i.e., functions outside the JVM ecosystem, and access memory that is foreign to the JVM.

It allows us to access libraries of other runtimes and languages, something which JNI (Java Native Interface) did but with more efficiency, performance boost, and security. This JEP brings broader support for invoking native libraries on all platforms where the JVM runs. Additionally, the API is extensive and more readable and provides ways to operate on structured and unstructured data of unlimited size across multiple memory types, such as heap and transient memory.

We’ll make a native call to C’s strlen() function to compute the length of a string using the new Foreign Function and Memory API:

public long getLengthUsingNativeMethid(String string) throws Throwable {
    SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();
    MethodHandle strlen =
      Linker.nativeLinker()
        .downcallHandle(
          stdlib.find("strlen").orElseThrow(),
          of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));

    try (Arena offHeap = Arena.ofConfined()) {
        MemorySegment str = offHeap.allocateFrom(string);

        long len = (long) strlen.invoke(str);
        System.out.println("Finding String length using strlen function: " + len);
        return len;
    }
}

5.2. Class File API – JEP 457

The Class File API standardizes the process of reading, parsing, and transforming Java .class files. Additionally, it aims to eventually deprecate the JDK’s internal copy of the third-party ASM library.

The Class File API provides several powerful APIs to transform and modify elements and methods inside a class selectively. As an example, let’s see how we can leverage the API to remove the methods in a class file that starts with test_:

ClassFile cf = ClassFile.of();
ClassModel classModel = cf.parse(PATH);
byte[] newBytes = cf.build(classModel.thisClass()
  .asSymbol(), classBuilder -> {
    for (ClassElement ce : classModel) {
        if (!(ce instanceof MethodModel mm && mm.methodName()
          .stringValue()
          .startsWith(PREFIX))) {
            classBuilder.with(ce);
        }
    }
});

This code parses the bytes of the source class file and transforms them by only taking the methods (represented by the MethodModel type) that satisfy our given condition. The resulting class file, which omits the test_something() method of the original class, can be verified.

5.3. Stream Gatherers – JEP 461

JEP 461 brings support for custom intermediate operations in the Streams API with Stream::gather(Gatherer). Developers have long wanted support for additional operations because of limited built-in stream intermediate operations. With this enhancement, Java allows us to create a custom intermediate operation.

We can achieve this by chaining the gather() method on a stream and supplying it with a Gatherer, which is an instance of the java.util.stream.Gatherer interface.

Let’s use Stream gatherers to group a list of elements in groups of 3 with a sliding window approach:

public List<List<String>> gatherIntoWindows(List<String> countries) {
    List<List<String>> windows = countries
      .stream()
      .gather(Gatherers.windowSliding(3))
      .toList();
    return windows;
}

// Input List: List.of("India", "Poland", "UK", "Australia", "USA", "Netherlands")
// Output: [[India, Poland, UK], [Poland, UK, Australia], [UK, Australia, USA], [Australia, USA, Netherlands]]

There are five built-in gatherers as part of this preview feature:

  • fold
  • mapConcurrent
  • scan
  • windowFixed
  • windowSliding

This API also empowers developers to define a custom Gatherer.

5.4. Structured Concurrency – JEP 462

Structured Concurrency API, an incubator feature in Java 19, was introduced as a preview feature in Java 21 and returns in Java 22 without any new changes.

The goal of this API is to introduce structure and coordination in Java concurrent tasks. Structured Concurrency API aims to improve the development of concurrent programs by introducing a pattern of coding style that aims to reduce the common pitfalls and drawbacks of concurrent programming.

This API streamlines error propagation, reduces cancellation delays, and improves reliability and observability.

5.5. Scoped Values – JEP 464

Java 21 introduced the Scoped Values API as a preview feature along with Structured Concurrency. This API moves into the second preview in Java 22 without any changes.

Scoped values enable storing and sharing immutable data within and across threads. Scoped values introduce a new type, ScopedValue<>. We write the values once, and they remain immutable throughout their lifecycle.

Web requests and server code typically use ScopedValues. They are defined as public static fields and allow data objects to be passed across methods without being defined as explicit parameters.

In the following example, let’s see how we can authenticate a user and store its context as a ScopedValue across multiple instances:

private void serve(Request request) {
    User loggedInUser = authenticateUser(request);
    if (loggedInUser) {
        ScopedValue.where(LOGGED_IN_USER, loggedInUser)
          .run(() -> processRequest(request));
    }
}

// In a separate class

private void processRequest(Request request) {
    System.out.println("Processing request" + ScopedValueExample.LOGGED_IN_USER.get());
}

Multiple login attempts from unique users scope the user information to its unique thread:

Processing request :: User :: 46
Processing request :: User :: 23

5.6. Vector API (Seventh Incubator) – JEP 460

Java 16 introduced the Vector API, and Java 22 brought its seventh incubator. This update provides performance improvements and minor updates. Previously, vector access was limited to heap MemorySegments, which were backed by a byte array. They are now updated to be backed by an array of primitive element types.

This update is low-level and does not impact the API usage in any way.

6. Tooling Updates

Java 22 brings an update on the tooling of Java build files.

6.1. Multi-File Source Programs – JEP 458

Java 11 introduced executing a single Java file without explicitly compiling it using the javac command. This was very efficient and quick. The downside is that when there are dependent Java source files, we cannot its advantage.

Starting with Java 22, we can finally run multi-file Java programs:

public class MainApp {
    public static void main(String[] args) {
        System.out.println("Hello");
        MultiFileExample mm = new MultiFileExample();
        mm.ping(args[0]);
    }
}

public class MultiFileExample {
    public void ping(String s) {
        System.out.println("Ping from Second File " + s);
    }
}

We can run the MainApp directly without explicitly running javac:

$ java --source 22 --enable-preview MainApp.java "Test"

Hello
Ping from Second File Test

Here are a few things to keep in mind:

  • the compilation order is not guaranteed when classes are scattered across multiple source files
  • the .java files whose classes are referenced by the main program are compiled
  • duplicate classes in source files are not allowed and will error out
  • we can pass –class-path option to use pre-compiled programs or libraries

7. Performance

The performance update this iteration of Java brings is an enhancement to the G1 Garbage Collector mechanism.

7.1. Region Pinning for G1 Garbage Collector – JEP 423

Pinning is the process of informing the JVM’s underlying garbage collector not to remove specific objects from memory. Garbage collectors that do not support this feature generally pause garbage collection until the JVM is instructed that the critical object is released.

This was a problem primarily seen in JNI critical section regions. The absence of the pinning functionality in a Garbage collector impacts its latency, performance, and overall memory consumption in the JVM.

With Java 22, the G1 Garbage Collector finally supports region pinning. This removes the need for Java threads to pause the G1 GC while using JNI.

8. Conclusion

Java 22 brings a plethora of updates, enhancements, and new preview features to Java.

As usual, all code samples can be found over on GitHub.

Course – LS (cat=Java)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments