top of page
  • khangaonkar

JDK22: New features in Java 22

JDK 22 was released on March 19, 2024. As always, a new suite of goodies is made available for the benefit of the Java programmer. Many of them are in preview. This is a very brief listing of the new features. Each of the features merits a blog of its own. Hoping this can get you started and you can pick and choose which feature to research in detail. Note that JDK22 is a feature release which means it has 6 months support. Major releases like JDK21 have support for much longer.



Prior to this feature, the JVM disabled GC whenever a thread was in a JNI critical region. The performance impact of this was so severe that many developers had stopped using such critical regions. The issue is addressed by maintaining a count of critical regions in each memory region. If the count is 0, the memory region can be garbage collected. Otherwise it is considered pinned. During a minor collection, pinned regions in the young generation are promoted to the old generation. During a major collection, pinned regions are not garbage collected.


Historically Java did not allow statements before the super(...) call in the constructor. It was a compiler error. The super is used to call the constructor of the parent class. This was done to ensure that you do not access the state of a class before the class is initialized.


This feature allows statements before a super call. However those statements cannot access, use or change the state of the class.


A typical use case of this feature is to say validate parameters before passing them to constructors of parent classes.

// Before
public class NegativeInteger extends Integer {

    public NegativeInteger(long value) {
        super(value);               // Potentially unnecessary work
        if (value >= 0)
            throw new IllegalArgumentException("Positive not allowed");
    }
}
// New
public class NegativeInteger extends Integer {

    public NegativeInteger(long value) {
        if (value >= 0)
            throw new IllegalArgumentException("Positive not allowed");
		super(value); 
    }
}

Third preview of an API to let java programs access functions and data outside the JVM. One of the stated goals is to replace the brittle JNI.

It covers 3 areas:

  • foreign memory allocation and deallocation

  • manipulate and access foreign memory

  • call foreign functions

The foreign function interface lets you call code written in native languages like C/C++.

The memory access API is for access native memory ( outside of heap).

The Linker interface provides access to foreign functions from java code.

Linker linker = Linker.nativeLinker();
MethodHandle strlen = linker.downcallHandle(
    linker.defaultLookup().find("strlen").orElseThrow(),
    FunctionDescriptor.of(JAVA_LONG, ADDRESS)
);

The MemorySegment interface provides access to contiguous area of memory.

MemorySegment segment = ...
int value = segment.get(ValueLayout.JAVA_INT, 0);

The Arena interface is responsible for the lifecycle of memory segments.

MemorySegment segment = Arena.global().allocate(100, 1);

This is a difficult topic that needs blog(s) of its own.


The preview tries to improve readability of code by matching records without the name or type. The underscore character _ is used as a placeholder for the unnamed variable or pattern.

for (Widget _: widgets) {
	widgetcount++ ;
}

try {
   doWork()
} catch(WorkException _) {
   logger.Error("Unable to complete work");
}


This preview feature provides an API for parsing, generating and transforming class files. The api is in the java.lang.classfile package.


In JDK 11, Java introduced the ability to run Java programs without an explicit compilation step.

If you have a prog Prog.java, you could type java Prog.java to run it. ( instead of javac Prog.java followed by java Prog).

However a limitation was that all source code has to be in a single file.

This feature removes the limitation by allowing multiple .java files.


// Hello.java
class Hello {
    public static void main(String[] args) { HelloHelper.run(); }
}

// HelloHelper.java
class HelloHelper {
	public void run() {
		System.out.println("Hello Java ");
	}
}

$ java Hello.java

This is a second preview.

Historically in Java, you could concatenate Strings with the + operator or format them with String.format. This was unwieldy and unsafe for large text like SQL, HTML or JSON. Many languages support interpolation which is again unsafe

`My name is {name}.`

Java decided to go a step further with a template processor.

String name = ""Bob";
String greeting = STR."My name is \{name}";

A template expression consists of

  • STR which is the template processor

  • followed by a dot .

  • the template "My name is \{name}" in which \{name} is the embedded expression.

In the 7th preview, this is the API for efficient vector operations who performance is superior to scalar operations. Given the importance of vector operation in AI, one can see why this is important.


This feature enhances the Stream API to support custom intermediate operations. Currently there are a few intermediate operations like mapping, filtering, reducing etc. Prior to this feature, if you wanted do some thing special in an intermediate operation, you have to write some ugly code or pray for the JDK to add support. This lets developers create their own intermediate operations.

// traditional stream

source.map(....)
      .map(.....)
       .filter(...)
       .
       .
       .collect();

// with gatherer
source.map(...)
      .gather(gatherer)
      .gather(gatherer)
      .
      .
      .collect();

	

The gatherer needs to implement the Gatherer interface.


This is described in more detail in my blog on Structured concurrency.


This is a 2nd preview of what was JEP 445 in JDK21.

The purpose of the preview feature is to make to easy for programmers to get started with Java without knowing advanced features. Consider the code


public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
 } 

To understand the code you need to know what public is , what class is, what static is and so on. With this feature, you could write the same program with an instance main method


class Hello {
    main() {
        System.out.println("Hello world");
    }
 } 

or even better using unnamed classes


void main() {
        System.out.println("Hello world");
}

In the last case, JDK implicitly adds a class if there is no surrounding class around the method.

If there is any conflict between main methods, then the JDK will run the main with String[] parameter and if it is not there then run the main without any parameter.


This 2nd preview feature allows for sharing of immutable values between threads. An improvement over thread local storage. Traditionally the way to pass variables is method parameters or thread local storage. Method parameters are not an option if you do not have control over parts of the code. Thread local storage is notorious for hard to find bugs and the situation is worse with the short lived virtual threads.


A scoped value is a container object that allows a data value to be safely and efficiently shared by a method with its direct and indirect callees within the same thread, and with child threads, without resorting to method parameters.

public class Service {
  public final static ScopedValue<User> USER = ScopedValue.newInstance();
  . . .
  private void handleRequest(Request request) {
    . . .
    User loggedInUser = login(request);
    ScopedValue.where(USER, loggedInUser)
               .run(() -> process(request));
    . . .
  }
}

// somewhere down the call stack
public Data doWork(UUID id) {
    Data data = findById(id);
    User loggedInUser = Server.USER.get();
    if (loggedInUser.isAdmin()) {
      readData(data);
    }
    return data;
  }

To conclude, Java continues to improve with rich features in JDK22. As mentioned earlier , this is just to give you a jumpstart. No one needs to know every feature in detail. Hopefully you see something that interest you and you can go into more detail for that feature.

0 comments

Recent Posts

See All

Go Tutorial: Receivers

When I was new to the Go programming language and saw this syntax func (m *Service) addNumbers(a int32, b int32) int32 I was confused. I had not seen syntax like (m *Service) between func and the func

Comments


bottom of page