The Java Development Kit (JDK) 23 release (in September 2024) marks a significant milestone in the history of the Java programming language, which is a robust, versatile, and crucial tool in the software development environment.
Since 1995, when Java was first presented by Sun Microsystems with the slogan “Write One, Run Anywhere”, it has been providing the possibility to execute code on any device. Over the years, Java has been evolving through various versions, presenting new features and improvements that maintain backward compatibility.
Tradition continues with this new version, which has the objective to improve developers’ productivity and application performance, while ensuring Java remains relevant and powerful.
This release includes a total of 12 Java Enhancement Proposals (JEPs), namely:
Let’s take a look into some of the most relevant updates.
This new feature has the following objectives:
Example:
//improve the switch expression:
switch (x.getStatus()) {
case 0 -> "ok";
case 1 -> "warning";
case 2 -> "error";
default -> "unknown status: " + x.getStatus();
}
//exposing the matched value:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
//allowing guards to inspect the corresponding value:
switch (x.getYearlyFlights()) {
case 0 -> ...;
case 1 -> ...;
case 2 -> issueDiscount();
case int i when i >= 100 -> issueGoldCard();
case int i -> ... appropriate action when i > 2 && i < 100 ...
}
More information about this feature here.
In the Java programming language, constructors allow statements to appear before an explicit constructor invocation, such as super(..) or this(..). While these statements cannot reference the instance under construction, they can initialise its fields.
Initialising fields before invoking another constructor enhances class reliability, particularly when methods are overridden. This feature is currently in preview.
Example:
//Flexible Constructor Bodies
class Parent {
int x;
public Parent(int x) {
this.x = x;
}
}
class Child extends Parent {
int y;
public Child(int x, int y) {
// Statements before calling the parent constructor
int temp = x * 2; // Cannot reference instance fields
super(temp); // Explicit constructor invocation
this.y = y; // Instance fields can be initialized after the invocation
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child(5, 10);
System.out.println("x: " + child.x + ", y: " + child.y); // Outputs: x: 10, y: 10
}
}
More information about this feature here.
The structured concurrency feature simplifies multithreaded programming by treating multiple tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation.
Example:
// Structured Concurrency
public class StructuredConcurrencyExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> future1 = scope.fork(() -> fetchDataFromService1());
Future<String> future2 = scope.fork(() -> fetchDataFromService2());
scope.join(); // Join both forks
scope.throwIfFailed(); // Propagate exceptions
String result1 = future1.resultNow();
String result2 = future2.resultNow();
System.out.println(result1 + " " + result2);
}
}
private static String fetchDataFromService1() {
// Simulate fetching data
return "Data1";
}
private static String fetchDataFromService2() {
// Simulate fetching data
return "Data2";
}
}
More information about this feature here.
Scoped values enable methods to share immutable data with their callees within a thread and with child threads. They are simpler to reason about compared to thread-local variables and offer lower space and time costs.
When used with virtual threads and structured concurrency, scoped values are particularly efficient. This feature is currently a preview API.
Example:
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import jdk.incubator.concurrent.ScopedValue;
public class Main {
public static void main(String[] args) {
ScopedValue<String> scopedValue = ScopedValue.create("Hello, World!");
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
System.out.println("Scoped Value: " + scopedValue.get());
});
executor.shutdown();
}
}
More information about this feature here.
This feature aims to enhance the Java Stream API by introducing two new terminal operations: ‘Stream.gather(toList(), toSet())’ and ‘Stream.gather(toMap(), toSet())’. These operations enable collecting elements of a stream into multiple collections simultaneously, which simplifies the code and improves readability when a stream needs to be collected into several containers.
The feature, initially proposed in Java 22, has been refined based on feedback and is now being presented for a second preview to ensure its functionality and performance meet community needs before being finalised.
Example:
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamGatherersExample {
public static void main(String[] args) {
// Example stream
Stream<String> stream = Stream.of("apple", "banana", "cherry", "apple", "date", "banana");
// Using Stream.gather to collect elements into a List and a Set simultaneously
var result = stream.collect(Collectors.gather(
Collectors.toList(),
Collectors.toSet()
));
// Extracting the results
List<String> list = result.getFirst();
Set<String> set = result.getSecond();
// Printing the results
System.out.println("List: " + list);
System.out.println("Set: " + set);
}
}
More information about this feature here.
Here are my tips to Java Developers using the new Java 23:
Adopt efficient coding techniques:
Error handling guidelines:
Performance optimisation strategies:
Java 23 introduces a set of advanced features and enhancements designed to improve the efficiency and performance of software development, while reinforcing Java’s compatibility and interoperability with other programming languages and platforms. This release includes improvements to the Java Virtual Machine (JVM), new language features, and updates to existing APIs and libraries, all aimed at providing developers with more tools to write high-performance, maintainable code. Notable features include enhancements to the pattern matching for switch expressions, improvements to the foreign function and memory API, and new garbage collection options, which collectively contribute to the robustness and adaptability of Java applications in diverse environments.
As a senior Java software developer, I find Java 23’s new features both exciting and essential for modern development. The enhancements to pattern matching simplify complex conditional logic, making code more readable and maintainable. The foreign function and memory API improvements are particularly important, as they open up new possibilities for integrating Java with native code and other languages, enhancing Java’s versatility in multi-language ecosystems.
Overall, Java 23 continues the tradition of evolving the language and platform to meet contemporary development needs, ensuring that Java remains a top choice for developers worldwide.
The Java Development Kit (JDK) 23 release (in September 2024) marks a significant milestone in the history of the Java programming language, which is a robust, versatile, and crucial tool in the software development environment.
Since 1995, when Java was first presented by Sun Microsystems with the slogan “Write One, Run Anywhere”, it has been providing the possibility to execute code on any device. Over the years, Java has been evolving through various versions, presenting new features and improvements that maintain backward compatibility.
Tradition continues with this new version, which has the objective to improve developers’ productivity and application performance, while ensuring Java remains relevant and powerful.
This release includes a total of 12 Java Enhancement Proposals (JEPs), namely:
Let’s take a look into some of the most relevant updates.
This new feature has the following objectives:
Example:
//improve the switch expression:
switch (x.getStatus()) {
case 0 -> "ok";
case 1 -> "warning";
case 2 -> "error";
default -> "unknown status: " + x.getStatus();
}
//exposing the matched value:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
//allowing guards to inspect the corresponding value:
switch (x.getYearlyFlights()) {
case 0 -> ...;
case 1 -> ...;
case 2 -> issueDiscount();
case int i when i >= 100 -> issueGoldCard();
case int i -> ... appropriate action when i > 2 && i < 100 ...
}
More information about this feature here.
In the Java programming language, constructors allow statements to appear before an explicit constructor invocation, such as super(..) or this(..). While these statements cannot reference the instance under construction, they can initialise its fields.
Initialising fields before invoking another constructor enhances class reliability, particularly when methods are overridden. This feature is currently in preview.
Example:
//Flexible Constructor Bodies
class Parent {
int x;
public Parent(int x) {
this.x = x;
}
}
class Child extends Parent {
int y;
public Child(int x, int y) {
// Statements before calling the parent constructor
int temp = x * 2; // Cannot reference instance fields
super(temp); // Explicit constructor invocation
this.y = y; // Instance fields can be initialized after the invocation
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child(5, 10);
System.out.println("x: " + child.x + ", y: " + child.y); // Outputs: x: 10, y: 10
}
}
More information about this feature here.
The structured concurrency feature simplifies multithreaded programming by treating multiple tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation.
Example:
// Structured Concurrency
public class StructuredConcurrencyExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> future1 = scope.fork(() -> fetchDataFromService1());
Future<String> future2 = scope.fork(() -> fetchDataFromService2());
scope.join(); // Join both forks
scope.throwIfFailed(); // Propagate exceptions
String result1 = future1.resultNow();
String result2 = future2.resultNow();
System.out.println(result1 + " " + result2);
}
}
private static String fetchDataFromService1() {
// Simulate fetching data
return "Data1";
}
private static String fetchDataFromService2() {
// Simulate fetching data
return "Data2";
}
}
More information about this feature here.
Scoped values enable methods to share immutable data with their callees within a thread and with child threads. They are simpler to reason about compared to thread-local variables and offer lower space and time costs.
When used with virtual threads and structured concurrency, scoped values are particularly efficient. This feature is currently a preview API.
Example:
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import jdk.incubator.concurrent.ScopedValue;
public class Main {
public static void main(String[] args) {
ScopedValue<String> scopedValue = ScopedValue.create("Hello, World!");
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
System.out.println("Scoped Value: " + scopedValue.get());
});
executor.shutdown();
}
}
More information about this feature here.
This feature aims to enhance the Java Stream API by introducing two new terminal operations: ‘Stream.gather(toList(), toSet())’ and ‘Stream.gather(toMap(), toSet())’. These operations enable collecting elements of a stream into multiple collections simultaneously, which simplifies the code and improves readability when a stream needs to be collected into several containers.
The feature, initially proposed in Java 22, has been refined based on feedback and is now being presented for a second preview to ensure its functionality and performance meet community needs before being finalised.
Example:
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamGatherersExample {
public static void main(String[] args) {
// Example stream
Stream<String> stream = Stream.of("apple", "banana", "cherry", "apple", "date", "banana");
// Using Stream.gather to collect elements into a List and a Set simultaneously
var result = stream.collect(Collectors.gather(
Collectors.toList(),
Collectors.toSet()
));
// Extracting the results
List<String> list = result.getFirst();
Set<String> set = result.getSecond();
// Printing the results
System.out.println("List: " + list);
System.out.println("Set: " + set);
}
}
More information about this feature here.
Here are my tips to Java Developers using the new Java 23:
Adopt efficient coding techniques:
Error handling guidelines:
Performance optimisation strategies:
Java 23 introduces a set of advanced features and enhancements designed to improve the efficiency and performance of software development, while reinforcing Java’s compatibility and interoperability with other programming languages and platforms. This release includes improvements to the Java Virtual Machine (JVM), new language features, and updates to existing APIs and libraries, all aimed at providing developers with more tools to write high-performance, maintainable code. Notable features include enhancements to the pattern matching for switch expressions, improvements to the foreign function and memory API, and new garbage collection options, which collectively contribute to the robustness and adaptability of Java applications in diverse environments.
As a senior Java software developer, I find Java 23’s new features both exciting and essential for modern development. The enhancements to pattern matching simplify complex conditional logic, making code more readable and maintainable. The foreign function and memory API improvements are particularly important, as they open up new possibilities for integrating Java with native code and other languages, enhancing Java’s versatility in multi-language ecosystems.
Overall, Java 23 continues the tradition of evolving the language and platform to meet contemporary development needs, ensuring that Java remains a top choice for developers worldwide.