New Features in Java JDK 23 (Sept 2024)

Java 23, released on September 17, 2024, marks another significant milestone in the evolution of the Java platform. This version introduces several new features and improvements that enhance Java's capabilities, performance, and developer productivity.



Table of Contents:

  1. Key Features Introduced in Java 23
  2. Detailed Explanation of Java 23 Features
    1. Primitive Types in Patterns, instanceof, and switch (Preview) - JEP 455
    2. Class-File API (Second Preview) - JEP 466
    3. Markdown Documentation Comments - JEP 467
    4. Vector API (Eighth Incubator) - JEP 469
    5. Stream Gatherers (Second Preview) - JEP 473
    6. Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal - JEP 471
    7. ZGC: Generational Mode by Default - JEP 474
    8. Module Import Declarations (Preview) - JEP 476
    9. Implicitly Declared Classes and Instance Main Methods (Third Preview) - JEP 477
    10. Structured Concurrency (Third Preview) - JEP 480
    11. Flexible Constructor Bodies (Second Preview) - JEP 482
    12. Scoped Values (Third Preview) - JEP 481
  3. Deprecations and Removals


Key Features Introduced in Java 23:

  • Primitive Types in Patterns, instanceof, and switch (Preview): Enhances pattern matching to work with primitive types.
  • Class-File API (Second Preview): Provides an API for parsing, generating, and transforming Java class files.
  • Markdown Documentation Comments: Introduces support for Markdown syntax in documentation comments.
  • Vector API (Eighth Incubator): Continues to refine the API for vector computations.
  • Stream Gatherers (Second Preview): Enhances the Stream API with new gathering operations.
  • ZGC: Generational Mode by Default: Improves garbage collection performance.
  • Module Import Declarations (Preview): Simplifies the use of types from other modules.
  • Implicitly Declared Classes and Instance Main Methods (Third Preview): Simplifies the creation of small programs.
  • Structured Concurrency (Third Preview): Simplifies multithreaded programming.
  • Scoped Values (Third Preview): Provides a mechanism for sharing immutable data.
  • Flexible Constructor Bodies (Second Preview): Allows more flexibility in constructor implementation.


Detailed Explanation of Java 23 Features:

1. Primitive Types in Patterns, instanceof, and switch (Preview) - JEP 455

This feature extends pattern matching to work with primitive types, allowing for more expressive and concise code when dealing with primitive values in patterns, instanceof checks, and switch expressions. Let's explore this with some examples:

Primitive Type Patterns in switch


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

In this example, the last case uses a primitive type pattern to capture any integer value not matched by the previous cases.

Primitive Type Patterns with instanceof


    Object value = 42;
    if (value instanceof Integer i) {
        System.out.println("Integer value: " + i);
    } else if (value instanceof Double d) {
        System.out.println("Double value: " + d);
    } else if (value instanceof Number n) {
        System.out.println("Number value: " + n);
    }
    

This example demonstrates how instanceof can now be used with primitive wrapper types, combining type checking and casting in a single step.

Primitive Types in switch


    long value = 1000000000000L;
    switch (value) {
        case 1L -> System.out.println("One");
        case 1000000000000L -> System.out.println("One trillion");
        case long x when x > 1000000000000L -> System.out.println("More than one trillion");
        default -> System.out.println("Other value");
    }
    

This example shows how switch now supports long values, allowing for a wider range of numeric comparisons.

These enhancements make the Java language more uniform and expressive, especially when working with primitive types in pattern matching contexts. For more detailed information, refer to JEP 455.

2. Class-File API (Second Preview) - JEP 466

The Class-File API provides a standard API for parsing, generating, and transforming Java class files. This preview API, defined in JEP 466, aims to track the class file format specified in the Java Virtual Machine Specification. It introduces key abstractions such as elements (immutable descriptions of class file parts), builders (for constructing compound elements), and transforms (for mediating element transformations).

Key features and design principles include:

  • Immutable representation of class file entities
  • Tree-structured representation of class files
  • User-driven navigation and lazy parsing
  • Unified streaming and materialized views
  • Emergent transformation capabilities
  • Automatic generation of tightly coupled entities
  • Leveraging modern Java language features for a more flexible and pleasant API

This API is particularly useful for bytecode analysis tools, compilers, and other low-level utilities. It aims to enable JDK components to migrate away from internal copies of third-party libraries like ASM, potentially improving the ecosystem's ability to quickly adopt new class file features.

For more detailed information, refer to the official JEP 466 documentation.

3. Markdown Documentation Comments - JEP 467

Java 23 introduces support for Markdown syntax in documentation comments, making it easier to write and maintain rich, formatted documentation directly in the source code. This feature, defined in JEP 467, allows developers to use Markdown alongside HTML elements and JavaDoc tags in API documentation comments.

Key Features:

  • New `///` syntax for Markdown documentation comments
  • Support for CommonMark variant of Markdown
  • Enhanced linking to program elements
  • Support for simple GFM pipe tables
  • Compatibility with all JavaDoc tags

Example:


    /// Returns a hash code value for the object. This method is
    /// supported for the benefit of hash tables such as those provided by
    /// [java.util.HashMap].
    ///
    /// The general contract of `hashCode` is:
    ///
    ///   - Whenever it is invoked on the same object more than once during
    ///     an execution of a Java application, the `hashCode` method
    ///     must consistently return the same integer, provided no information
    ///     used in `equals` comparisons on the object is modified.
    ///   - If two objects are equal according to the
    ///     [equals][#equals(Object)] method, then calling the
    ///     `hashCode` method on each of the two objects must produce the
    ///     same integer result.
    ///   - It is _not_ required that if two objects are unequal
    ///     according to the [equals][#equals(Object)] method, then
    ///     calling the `hashCode` method on each of the two objects
    ///     must produce distinct integer results.
    ///
    /// @return  a hash code value for this object.
    /// @see     java.lang.Object#equals(java.lang.Object)
    /// @see     java.lang.System#identityHashCode
    

This new feature aims to simplify the process of writing documentation, improve readability of source code, and maintain compatibility with existing documentation practices.

4. Vector API (Eighth Incubator) - JEP 469

The Vector API continues to evolve in its eighth incubation, providing a mechanism for expressing vector computations that reliably compile at runtime to optimal vector instructions on supported CPU architectures. This API aims to achieve performance superior to equivalent scalar computations.

Key Features:

  • Clear and concise API for expressing a wide range of vector computations
  • Platform agnostic design, enabling implementations on multiple architectures
  • Reliable runtime compilation and performance on x64 and AArch64 architectures
  • Graceful degradation when vector instructions are not fully supported

Example Usage:


    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()) {
            var va = FloatVector.fromArray(SPECIES, a, i);
            var vb = FloatVector.fromArray(SPECIES, b, i);
            var vc = va.mul(va)
                       .add(vb.mul(vb))
                       .neg();
            vc.intoArray(c, i);
        }
        for (; i < a.length; i++) {
            c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
        }
    }
    

This example demonstrates how the Vector API can be used to perform efficient vector computations, which can be compiled to optimal vector instructions on supported architectures.

For more detailed information, refer to the official JEP 469 documentation.

5. Stream Gatherers (Second Preview) - JEP 473

Stream Gatherers, introduced as a preview feature in JEP 473, significantly enhance the Stream API by allowing custom intermediate operations. This feature enables more flexible and expressive stream pipelines, capable of transforming data in ways not easily achievable with existing built-in operations.

Key Aspects:

  • Introduces Stream::gather(Gatherer) as a new intermediate operation
  • Gatherers can transform elements in one-to-one, one-to-many, many-to-one, or many-to-many fashion
  • Supports stateful operations, short-circuiting, and parallel execution
  • Includes built-in gatherers like fold, mapConcurrent, scan, windowFixed, and windowSliding

Example Usage:


    // Example: Using windowFixed gatherer
    List<List<Integer>> result = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                                      .gather(new WindowFixed<>(3))
                                      .toList();
    // result ==> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

    // Example: Custom gatherer for selecting one element
    Gatherer<Integer, ?, Integer> selectLargest = selectOne(Math::max);
    Optional<Integer> largest = Stream.generate(() -> ThreadLocalRandom.current().nextInt())
                                       .limit(1000)
                                       .gather(selectLargest)
                                       .parallel()
                                       .findFirst();
    

Stream Gatherers provide a powerful way to extend the Stream API, allowing developers to implement custom intermediate operations that were previously difficult or impossible to express within the stream paradigm.

For more detailed information, refer to the official JEP 473 documentation.

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

This JEP deprecates the memory-access methods in sun.misc.Unsafe for removal in a future release. These unsupported methods have been superseded by standard APIs, namely the VarHandle API (JEP 193, JDK 9) and the Foreign Function & Memory API (JEP 454, JDK 22). This change aims to prepare the ecosystem for the eventual removal of these methods and help developers realize when their applications rely on them.

Key Points:

  • 79 out of 87 methods in sun.misc.Unsafe are for accessing memory and will be deprecated.
  • A new command-line option --sun-misc-unsafe-memory-access is introduced to control the behavior of these methods.
  • Deprecation and removal will occur in phases across multiple JDK releases.
  • Standard APIs like VarHandle and MemorySegment are recommended as replacements.

For more details, refer to the official JEP 471 documentation.

7. ZGC: Generational Mode by Default - JEP 474

This feature changes the default mode of the Z Garbage Collector (ZGC) to generational mode, signaling a focus on Generational ZGC for future development. Key points include:

  • The default value of the `ZGenerational` option is changed from `false` to `true`.
  • Non-generational mode is deprecated, with plans for removal in a future release.
  • Using `-XX:+UseZGC` now activates Generational ZGC by default.
  • Warnings are issued for deprecated options and the use of non-generational mode.

This change aims to reduce maintenance costs and streamline ZGC development. However, some workloads may require configuration adjustments. For more details, refer to the official JEP 474 documentation.

8. Module Import Declarations (Preview) - JEP 476

This preview feature introduces a new syntax for importing types from other modules, simplifying the use of the module system in Java applications. It allows developers to import all exported packages from a module with a single declaration.

Key Points:

  • Introduces the import module syntax
  • Simplifies reuse of modular libraries
  • Does not require the importing code to be in a module itself
  • Automatically imports transitive dependencies

Example Usage:


    // Instead of multiple imports like:
    // import java.util.Map;
    // import java.util.function.Function;
    // import java.util.stream.Collectors;
    // import java.util.stream.Stream;

    import module java.base;

    String[] fruits = new String[] { "apple", "berry", "citrus" };
    Map<String, String> m =
        Stream.of(fruits)
              .collect(Collectors.toMap(s -> s.toUpperCase().substring(0,1),
                                        Function.identity()));
    

This feature is particularly useful for prototyping, exploring new APIs, and simplifying imports in early-stage development. However, it may lead to name ambiguities, which can be resolved using single-type-import declarations.

For more detailed information, refer to the official JEP 476 documentation.

9. Implicitly Declared Classes and Instance Main Methods (Third Preview) - JEP 477

This preview feature aims to simplify Java programming for beginners and small programs by allowing:

  • Instance main methods that don't need to be static or have a String[] parameter
  • Implicitly declared classes, eliminating the need for explicit class declarations in simple programs
  • Automatic import of useful I/O methods (println, print, readln) from a new java.io.IO class
  • Automatic import of all public top-level classes and interfaces from the java.base module

These changes allow for more concise and beginner-friendly code, while providing a smooth path to scale up to larger programs. For example, a simple "Hello, World!" program can now be written as:


    void main() {
        println("Hello, World!");
    }
    

For more details, refer to the official JEP 477 documentation.

10. Structured Concurrency (Third Preview) - JEP 480

This preview feature introduces an API for structured concurrency, which simplifies multithreaded programming by treating multiple related tasks running in different threads as a single unit of work. Key benefits include:

  • Streamlined error handling and cancellation
  • Improved reliability
  • Enhanced observability of concurrent code

The API centers around the StructuredTaskScope class, which allows developers to fork subtasks and join them as a unit. Two predefined subclasses, ShutdownOnFailure and ShutdownOnSuccess, implement common concurrency patterns.

This feature is particularly useful for managing concurrent I/O operations and can work well with virtual threads. For more details, refer to the official JEP 480 documentation.

11. Flexible Constructor Bodies (Second Preview) - JEP 482

This preview feature allows for more flexibility in constructor implementation, particularly useful for record classes and other scenarios where the current constructor syntax is limiting.



Deprecations and Removals:

Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal (JEP 471): This JEP deprecates certain memory-access methods in the sun.misc.Unsafe class, encouraging developers to use safer alternatives.

12. Scoped Values (Third Preview) - JEP 481

This preview feature introduces scoped values, which provide a way to share immutable data both with callees within a thread and with child threads. Key benefits include:

  • Easier to reason about than thread-local variables
  • Lower space and time costs, especially when used with virtual threads and structured concurrency
  • Bounded lifetime, reducing the risk of memory leaks
  • Immutability, preventing unexpected modifications

Scoped values are particularly useful for passing context information through a call chain without explicitly adding parameters to every method. Here's a basic example of how to use scoped values:


    final static ScopedValue<String> CONTEXT = ScopedValue.newInstance();

    void someMethod() {
        ScopedValue.runWhere(CONTEXT, "contextValue", () -> {
            // Code here and in any methods called from here can access CONTEXT
            String value = CONTEXT.get();
            // Use the value...
        });
    }
    

This feature addresses common issues with thread-local variables, such as unconstrained mutability and unbounded lifetime, while providing a more efficient mechanism for sharing data across threads, especially when working with virtual threads.

For more detailed information, refer to the official JEP 481 documentation.

This preview feature allows for more flexibility in constructor implementation, particularly useful for record classes and other scenarios where the current constructor syntax is limiting. Key benefits include:

  • Ability to validate or prepare arguments before calling the superclass constructor
  • More natural placement of initialization logic
  • Improved readability and maintainability of constructor code

Here's an example demonstrating the new flexible constructor body syntax:


    public class PositiveBigInteger extends BigInteger {
        public PositiveBigInteger(long value) {
            if (value <= 0) throw new IllegalArgumentException("Value must be positive");
            super(value);
            // Additional initialization can be done here
        }
    }
    

This new syntax allows for more intuitive and efficient constructor implementations, especially in scenarios involving argument validation, preparation, or sharing. It's important to note that code before the superclass constructor invocation cannot reference the instance being constructed (except for initializing fields), maintaining the guarantee of top-down construction.

For more detailed information about this feature, refer to the official JEP 482 documentation.



Java 23 represents a significant step forward in Java's evolution, introducing new language features, improving performance, and enhancing developer productivity. While many features are still in preview or incubator status, they demonstrate Java's commitment to modernizing the language and addressing the needs of contemporary software development.

The introduction of Markdown support for documentation comments and the continued refinement of features like the Vector API and Structured Concurrency show Java's focus on both developer experience and performance. The move to make ZGC's generational mode the default is particularly noteworthy, as it promises significant performance improvements for many applications.

As Java continues to evolve, these new features and improvements in Java 23 pave the way for more expressive, efficient, and maintainable code. Developers are encouraged to explore these new capabilities and provide feedback to help shape the future of the Java platform.



For more detailed information about Java 23 and its features, please refer to the official OpenJDK project page for JDK 23.

Comments & Discussion

Facing issues? Have questions? Post them here! We're happy to help!