Icon for gfsd IntelliJ IDEA

What to expect in Java 23?

Learn about the features and updates to look forward to in Java 23

This post provides an overview of the features expected to be delivered in Java 23 (JDK 23) in September 2024.

JDK 23 is set to be released on 17 September 2024. There are already 12 features listed on the OpenJDK website for this release of Java 23. Find the early access builds for this release over there, and read this post for detailed descriptions of all the features currently known to make it into Java 23.

455: Primitive Types in Patterns, instanceof, and switch (Preview)

This preview feature is intended to enhance pattern matching and enable uniform data exploration. It allows primitive type patterns in all pattern contexts (e.g. in primitive and reference types and both nested and top-level contexts). It also extends instanceof and switch to work with all primitive types.

Example for Primitive Types in Patterns, instanceof, and switch:

Here’s what a switch case would look like without primitive type patterns:

switch (x.getStatus()) {
    case 0 -> "okay";
    case 1 -> "warning";
    case 2 -> "error";
    default -> "unknown status: " + x.getStatus();

With this preview feature in JDK 23, the above code can be adapted to:

switch (x.getStatus()) {
    case 0 -> "okay";
    case 1 -> "warning";
    case 2 -> "error";
    case int i -> "unknown status: " + i;

466: Class-File API (Second Preview)

In preview in JDK 22, Class-File API gets its second preview in this release.

This update adds a standard API for parsing, generating, and transforming Java class-files. The Class-File API tracks the class file format defined by JVM and enables JDK components to migrate to the standard API. The eventual goal is to remove the JDK’s internal copy of the third-party ASM library.

There are two changes in JDK 23 compared to the first preview phase in JDK 22:

  • The CodeBuilder class has been streamlined, with duplicate or infrequently used methods removed and some methods renamed.
  • The ClassSignature class has also been updated to increase the accuracy of modeling the generic signatures of superclasses and superinterfaces.

467: Markdown Documentation Comments

Java’s developers wanted to simplify writing and reading API documentation comments in source form. After this update, you will be able to use markdown syntax in documentation comments (as well as HTML elements and JavaDoc tags).

The update also extends the Compiler Tree API to ensure other tools can adequately handle the Markdown content in comments. This should make it more convenient for developers to manage comments in the documentation of their applications.

Try Symflower in your IDE to generate unit test templates & test suites

469: Vector API (Eighth Incubator)

Part of Project Valhalla, Vector API reaches its eighth incubator stage in JDK 23.

We introduced this feature in our post that provides descriptions for the updates in JDK 21. As a quick reminder, this update introduces an API to express vector computations that compile at runtime to optimal vector instructions. The Vector API enables a wide range of vector computations to be clearly expressed and is expected to result in performance improvements.

🤔 Curious about Vector API & other JDK 21 features?

Read our earlier post about Java 21 with descriptions of Vector API and all the other features in that release!

Supported CPU architectures include x64 and AArch64 architectures. Developers of JDK proposed to re-incubate the feature this time without any significant changes compared to JDK 22.

Example for Vector API:

Here is a simple scalar computation over elements of arrays:

void scalarComputation(float[] a, float[] b, float[] c) {
   for (int i = 0; i < a.length; i++) {
        c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;

Here is an equivalent vector computation using the Vector API:

static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;

void vectorComputation(float[] a, float[] b, float[] c) {
    int i = 0;
    int upperBound = SPECIES.loopBound(a.length);
    for (; i < upperBound; i += SPECIES.length()) {
        // FloatVector va, vb, vc;
        var va = FloatVector.fromArray(SPECIES, a, i);
        var vb = FloatVector.fromArray(SPECIES, b, i);
        var vc = va.mul(va)
        vc.intoArray(c, i);
    for (; i < a.length; i++) {
        c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;

473: Stream Gatherers (Second Preview)

Similarly, Stream Gatherers haven’t received any changes either: this feature is re-previewed without further changes in JDK 23 to gain more feedback.

A quick memo about Stream Gatherers: this feature enables the Stream API (JDK’s API for processing data streams) to support custom intermediate operations. These help you transform data in ways that the currently available built-in intermediate operations don’t allow. This update improves the flexibility and expressiveness of Stream pipelines.

😎 Get the low-down on Stream Gatherers

Check out our post about Java 22 which offres a detailed description of Stream Gatherers!

471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

The memory-access methods in sun.misc are now deprecated (but were deemed unsafe for removal in upcoming releases). These methods are no longer needed as they were superseded by two standard APIs: the VarHandle API and the Foreign Functions & Memory API.

Developers are encouraged to migrate from sun.misc.Unsafe to the above-mentioned replacements.

474: ZGC: Generational Mode by Default

The ZGC (Z Garbage Collector) is switched to generational mode as the non-generational mode is waiting for removal in an upcoming release. That’s because maintaining non-generational ZGC would have slowed down the development of new features.

A quick reminder: generational ZGC can collect young objects more frequently as they are likely to die sooner, and is deemed a better solution for most use cases than other garbage collectors.

☝️ Learn about ZGC

Read our detailed description of the Z Garbage Collector that was finalized in JDK 21.

476: Module Import Declarations (Preview)

To make it easier to reuse modular libraries, this feature introduces declarations that help you simply import all the packages exported by a module. The feature also helps beginners new to the Java language since they don’t have to memorize where third-party libraries or fundamental Java classes are located.

Example for module import declarations:

A module import declaration would look as follows:

import module M;

This imports all the public top-level classes and interfaces in:

  • The packages that module M exports to the current module.
  • All the packages that the modules being read by the current module (due to reading the module M) export.

477: Implicitly Declared Classes and Instance Main Methods (Third Preview)

Another feature that beginners will benefit from is Implicitly Declared Classes and Instance Main Methods. This update makes it easier to write declarations for simple, single-class programs.

See the example from our post about Java 22.

Without implicitly declared classes and instance main methods, the classic HelloWorld would look like this:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");

This feature lets students skip the use of String[] args as a parameter. So the code snippet is simplified to:

void main() {
    System.out.println("Hello, World!");

480: Structured Concurrency (Third Preview)

Structured concurrency is reaching its third preview stage. The ultimate goal of this new feature is to simplify concurrent programming, promoting a way that lets developers cut the risks associated with cancellation and shutdown (like thread leaks or cancellation delays).

Simply put, structured concurrency lets you as a developer manage related tasks that run in separate threads as one unit of work. In this version, the API is re-previewed without any changes.

🤓 What is structured concurrency?

Read our description of structured concurrency in our post introducing the features of Java 22.

Example for structured concurrency:

The method handle() represents a task in a server application. It handles an incoming request by submitting two subtasks:

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

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

        // Here, both subtasks have succeeded, so compose their results
        return new Response(user.get(), order.get());

Using StructuredTaskScope brings a few benefits:

  • Clear structure with subtasks either completed or being cancelled.
  • Easier error handling with short-circuiting: in case either findUser() or fetchOrder() fails, the other subtask will be canceled.
  • Cancellation propagation e.g. if the thread running handle() is interrupted before (or during) the call to join() is made, both subtasks will be automatically canceled.

481: Scoped Values (Third Preview)

This feature was previewed in Java 21 and is currently in second preview phase in JDK 22.

With this new feature, you can share immutable data both within threads and with child threads – all without using method arguments. Scoped values are geared towards robustness, performance, and comprehensibility. They have lower space and time costs and you can use them in combination with virtual threads and structured concurrency.

💡On scoped values…

Read our description of scoped values in our post about Java 22.

With scoped values, it’s easier to reason about data flow. If you’re using a large number of virtual threads, scoped values come in handy and should be preferred over thread-local variables. That’s because thread-local variables are mutable, have an unbounded lifetime, and need to be allocated in inheriting child threads in all cases, so there are a few downsides to their use.

There’s only one change vs the current 2nd preview: the type of theScopedValue.callWhere method’s operation parameter is a new functional interface that lets the Java compiler infer whether a checked exception may be thrown. Note that at the same time, ScopeValue.getWhere is removed.

482: Flexible Constructor Bodies (Second Preview)

Introduced (and previewed) in JDK 22 as Statements before super(…), flexible constructor bodies let you initialize fields in the same class before invoking a constructor.

Before this change (specifically, once it was introduced for preview in JDK 22), super(...) couldn’t be preceded by any other statements in a constructor. Since JDK 22, statements can come before super(...), making it easier to prepare, validate, and share arguments in constructors.

Constructors are still run in top-down order during the instantiation of the class. This provides you as a developer with some more freedom when expressing the behavior of constructors in your code.

Example for flexible constructor bodies:

public class PositiveBigInteger extends BigInteger {
    public PositiveBigInteger(long value) {
        if (value <= 0) throw new IllegalArgumentException(...);

Summary: all the news in JDK 23

Overall, there’s good reason to be excited about Java 23. We’re psyched about the 2nd preview of stream gatherers because this feature gives us as developers new ways to transform data. Structured concurrency is also great because it simplifies concurrent programming while improving reliability and enhancing observability. And we also like flexible constructor bodies because they let us fail fast when calling constructors while also guaranteeing that constructors run in top-down order. Finally, as avid markdown users, we’re also happy about markdown documentation comments because it provides a cleaner, easier way to edit documentation than HTML.

Sign up for our newsletter and follow us on social media (X, LinkedIn, or Facebook) to make sure you never miss any of our upcoming content!

| 2024-06-26