What’s New in Java 23: A Comprehensive Overview

Muratcan Yeldan
7 min readSep 27, 2024

Java 23 has officially arrived, bringing a host of new features and enhancements designed to boost developer productivity, improve performance, and make the language more accessible to newcomers. As a non-LTS (Long-Term Support) release, Java 23 introduces both finalized and preview features that continue to evolve the Java platform. In Java, these new features are commonly referred to as JDK Enhancement Proposals (JEPs). In this article, we will explore some of the significant additions with accompanying examples.

1 — Primitive Types in Patterns, instanceof, and switch (JEP455, Preview)

Java 23 enhances pattern matching by allowing primitive type patterns in all pattern contexts. It extends the instanceofand switch constructs to work seamlessly with all primitive types, enabling more expressive and concise code. This enhancement eliminating the need for unnecessary boxing.

Key Features

  • Primitive Type Patterns: You can now use primitive types in pattern matching constructs, such as instanceof and switch.
  • Enhanced instanceof Operator: Allows type checking and casting for primitive types without the need for explicit casting.
  • Improved Record Patterns: Eliminates the need for potentially lossy casts when decomposing records with primitive components.

Code Examples

Using Primitive Type Patterns with instanceof

Object value = 42;

if (value instanceof int i) {
System.out.println("The integer value is: " + i);
} else {
System.out.println("Not an integer");
}

Pattern Matching in switch with Primitive Types

int statusCode = getStatusCode();

String message = switch (statusCode) {
case 200 -> "OK";
case 404 -> "Not Found";
case int code when code >= 500 -> "Server Error";
default -> "Unknown Status: " + statusCode;
};

Record Patterns with Primitive Components

record Point(int x, int y) {}

Object obj = new Point(10, 20);

if (obj instanceof Point(int x, int y)) {
System.out.println("Point coordinates: (" + x + ", " + y + ")");
}

2. Class-File API (JEP466, Second Preview)

The Class-File API provides a standard way to parse, generate, and transform Java class files. This second preview includes refinements based on feedback, making it easier to work with class files programmatically. This API adheres to the JVM Class File Format Specification, providing a reliable way to interact with class files without relying on third-party libraries like ASM.

Key Features

  • Immutable Representation: Class-file entities are represented as immutable objects.
  • Tree-Structured Navigation: Allows hierarchical traversal of class files, including classes, methods, fields, and attributes.
  • User-Driven Parsing: Parse only the parts of the class file that are relevant, improving performance.
  • Unified Streaming and Materialized Views: Supports both streaming and in-memory representations.
  • Simplified Transformation: Easier manipulation and transformation of class files without dealing with low-level bytecode details.

Code Examples

Parsing a Class File

ClassFile cf = ClassFile.readAllBytes(Paths.get("MyClass.class"));

cf.methods().forEach(method -> {
System.out.println("Method: " + method.name());
});

Generating a New Class File

ClassBuilder classBuilder = new ClassBuilder("com.example.MyGeneratedClass");

classBuilder.addMethod(new MethodBuilder("myMethod")
.setReturnType(void.class)
.setPublic()
.withCode(codeBuilder -> {
codeBuilder.getStatic(System.class, "out", PrintStream.class)
.invokeVirtual("println", void.class, String.class)
.withConstant("Hello from generated code!");
})
);

byte[] classBytes = classBuilder.build();
Files.write(Paths.get("MyGeneratedClass.class"), classBytes);

3. Markdown Documentation Comments(JEP467)

JavaDoc now supports Markdown syntax in documentation comments, making it easier to write and read documentation. This feature addresses the verbosity and complexity associated with traditional HTML-based JavaDoc comments.

Key Features

  • Simplified Syntax: Use Markdown for formatting, resulting in cleaner and more readable documentation.
  • Compatibility: Existing JavaDoc comments remain valid; Markdown can be adopted incrementally.
  • Enhanced Readability: Both the source code and generated documentation become more approachable.

Code Examples

Traditional JavaDoc Comment

/**
* Adds two numbers.
* <p>
* This method returns the sum of {@code a} and {@code b}.
*
* @param a the first number
* @param b the second number
* @return the sum of {@code a} and {@code b}
*/

public int add(int a, int b) {
return a + b;
}

Using Markdown Syntax

/// Adds two numbers.
///
/// This method returns the sum of `a` and `b`.
///
/// @param a the first number
/// @param b the second number
/// @return the sum of `a` and `b`
public int add(int a, int b) {
return a + b;
}

4. Vector API (JEP469, Eighth Incubator)

The Vector API allows developers to perform vector computations that compile at runtime to optimal vector instructions, achieving superior performance compared to equivalent scalar computations.

Key Features

  • High-Performance Computations: Utilize SIMD (Single Instruction Multiple Data) instructions for performance-critical tasks.
  • Platform-Agnostic: Works across different CPU architectures supporting vector instructions.
  • Concise API: Express complex vector algorithms clearly and concisely.

Code Examples

Vectorized Computation Example

import jdk.incubator.vector.*;

public class VectorComputation {

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

5. Stream Gatherers (JEP473, Second Preview)

Stream Gatherers introduce a new way to collect items from streams efficiently. This feature provides more control over how streams are processed and collected.

Key Features

  • Efficient Collection: Optimized mechanisms for gathering stream elements.
  • Enhanced Control: Allows developers to define custom collection strategies.

Code Examples

Using Stream Gatherers

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(StreamGatherer.toList());

6. Deprecate Memory-Access Methods in sun.misc.Unsafe(JEP471)

The memory-access methods in sun.misc.Unsafe have been deprecated for removal. These methods are being replaced by safer, standard APIs that provide similar functionality without the associated risks.

Key Features

  • Safer Alternatives: Use VarHandle and MemorySegment for memory access operations.
  • Enhanced Security: Reduces the risk of failures and crashes due to unsafe memory operations.

Code Examples

Before (Using sun.misc.Unsafe)

private static final Unsafe UNSAFE = ...;
private static final long VALUE_OFFSET;

static {
try {
VALUE_OFFSET = UNSAFE.objectFieldOffset(MyClass.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}

public void setValue(int newValue) {
UNSAFE.putIntVolatile(this, VALUE_OFFSET, newValue);
}

After (Using VarHandle)

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class MyClass {
private static final VarHandle VALUE_HANDLE;

static {
try {
VALUE_HANDLE = MethodHandles.lookup().findVarHandle(MyClass.class, "value", int.class);
} catch (ReflectiveOperationException ex) {
throw new Error(ex);
}
}

private volatile int value;

public void setValue(int newValue) {
VALUE_HANDLE.setVolatile(this, newValue);
}
}

7. ZGC: Generational Mode by Default(JEP474)

The Z Garbage Collector (ZGC) now operates in generational mode by default. This change aims to improve performance and reduce maintenance costs by focusing future development on Generational ZGC.

Key Features

  • Improved Performance: Generational mode can offer better garbage collection performance for most applications.
  • Simplified Maintenance: Deprecating the non-generational mode reduces complexity.

8. Module Import Declarations (JEP476, Preview)

Module Import Declarations allow you to import all packages exported by a module with a single statement. This simplifies the reuse of modular libraries without requiring your code to be modularized.

Key Features

  • Simplified Imports: Use import module to bring in all exported packages.
  • Ease of Use: Facilitates the use of third-party libraries and APIs.

Code Examples

Before Java 23

import java.sql.Connection;
import java.sql.DriverManager;
import org.json.JSONObject;

public class DatabaseExample {
public void connect() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:myDriver:myDatabase");
// Use the connection...
}
}

With Module Import Declarations

import module java.sql;

public class DatabaseExample {
public void connect() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:myDriver:myDatabase");
// Use the connection...
}
}

9. Implicitly Declared Classes and Instance Main Methods (JEP477, Third Preview)

This feature simplifies the process of writing small programs by allowing instance main methods and implicitly declared classes. It's particularly useful for scripting and rapid prototyping.

Key Features

  • Simplified Entry Points: Write Java code without explicit class declarations.
  • Improved Scripting Support: Enhances Java’s suitability for quick scripts and one-off programs.

Example

Traditional Java Program

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

With Implicit Class and Instance Main Method

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

10. Structured Concurrency (JEP480, Third Preview)

Structured Concurrency simplifies multithreaded programming by introducing a structured way to manage threads and tasks, improving reliability and readability.

Key Features

  • Structured Thread Management: Automatically manages the lifecycle of threads.
  • Improved Error Handling: Propagates exceptions from child tasks to parent tasks.

Code Examples

Using Structured Concurrency

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> findUser());
Future<Integer> order = scope.fork(() -> fetchOrder());

scope.join(); // Join all tasks
scope.throwIfFailed(); // Propagate exceptions

// Use results
System.out.println("User: " + user.resultNow());
System.out.println("Order: " + order.resultNow());
}

11. Scoped Values (JEP481, Third Preview)

Scoped Values provide a way to share immutable data within and across threads, improving performance and safety in concurrent applications.

Key Features

  • Immutable Data Sharing: Safely share data without synchronization overhead.
  • Thread Confinement: Scoped values are confined to the scope in which they are set.

Code Examples

Using Scoped Values

ScopedValue<String> USER_ID = new ScopedValue<>();

void processRequest() {
USER_ID.bind("user123", () -> {
handleRequest();
});
}

void handleRequest() {
System.out.println("Processing request for user: " + USER_ID.get());
}

12. Flexible Constructor Bodies (JEP482, Second Preview)

Flexible Constructor Bodies allow for more expressive constructor definitions, including the ability to defer initialization and to use pattern matching within constructors.

Key Features

  • Deferred Initialization: Delay the initialization of certain fields.
  • Pattern Matching in Constructors: Use pattern matching to simplify constructor logic.

Code Examples

Before Java 23

public class SubClass extends SuperClass {
public SubClass(int value) {
super(validate(value));
}

private static int validate(int value) {
if (value < 0) throw new IllegalArgumentException("Value must be positive");
return value;
}
}

Using Pattern Matching in Constructors

public class Shape {
double area;

public Shape(Object shape) {
switch (shape) {
case Circle(double radius) -> this.area = Math.PI * radius * radius;
case Rectangle(double width, double height) -> this.area = width * height;
default -> throw new IllegalArgumentException("Unknown shape");
}
}
}

References:

JDK 23 Changes

Thank you for reading my article. Happy Coding !

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response