JDK 23, scheduled to be released on September 17, 2024, is set to bring a host of significant enhancements and advancements to the Java programming language and its supporting ecosystem. This release marks the second consecutive non-Long-Term Support (non-LTS) version of the JDK, following the previous LTS release of JDK 21.
It's worth noting that the next scheduled LTS release of the Java Development Kit is JDK 25, expected to be released in September 2025, and it is anticipated to follow the same long-term support model as previous LTS versions.
💡 JDK 23 is now in the Release Candidate stage, meaning its features are finalized. No new JEPs will be added to this release.
💡A Release Candidate (RC) in the context of JDK is like the final version of the software before it's officially launched. It's almost ready for public release, but it goes through one last round of testing to catch any minor issues or bugs that might have been missed. If no major problems are found, this RC version will be the one that gets released to everyone. It's the final step in making sure the software is stable and ready for widespread use.
To get started with JDK 23, you can download it from https://jdk.java.net/23/
JDK 23 includes a dozen new features, most of which are in preview mode, with only a few being finalized.
💡Oracle Corporation developed JEP as a system for gathering suggestions on how to enhance the Java Development Kit and OpenJDK.
These JEPs are divided into three categories based on features:
💡 To learn about the features of JDK 22, please check out our blog on JDK 22. We have covered most of it in great detail.
💡 These new JEPs mostly come from current major projects in the Java ecosystem like Amber, Loom, Panama, and Valhalla. Amber’s working on making the Java language better, Loom is all about making multi-threading easier, Panama is improving how Java interacts with native code, and Valhalla is enhancing Java’s type system.
Primarily designed for Java bytecode manipulation. Previously known as JEP 457 in JDK 22, it may not be essential for most regular developers.
While the Class-File API is a powerful tool, it’s primarily targeted at developers who need to manipulate Java bytecode. This includes:
Framework developers: who need to dynamically generate or modify classes at runtime.
Tooling developers: who need to inspect and analyze class files for purposes like profiling, optimization, or debugging.
Advanced Java developers: working on performance tuning, method interception, or bytecode-level transformations.
Regular application developers likely won’t need this API for day-to-day programming tasks. Most Java developers write code that gets compiled into class files without ever needing to manually inspect or modify the bytecode. However, for those working with JVM internals or building tools and frameworks, this API offers a significant upgrade in terms of usability and control.
Java's ecosystem has long relied on bytecode manipulation for various tasks, from framework development to runtime optimization. Historically, developers have relied on third-party libraries such as ASM, BCEL, or Javassist to perform these crucial tasks. However, with the introduction of the Class-File API, Java is taking a significant step towards reducing this dependency on external libraries.
Before we dive into the Class-File API, let's briefly explain what bytecode and class files are for those new to the concept:
Bytecode manipulation is a powerful technique in Java programming that allows developers to modify or analyze compiled Java classes. Here are some key reasons why bytecode manipulation is important:
Currently, when developers need to work with bytecode, they often turn to libraries like ASM. These libraries allow you to read, write, and modify class files. They're used for tasks such as:
While these libraries are powerful, they come with some challenges:
ASM stands for Abstract Syntax Manipulation. It's a popular Java library used for reading, writing, and transforming Java bytecode directly. Essentially, ASM allows developers to interact with Java class files at the bytecode level, enabling tasks such as:
ASM is widely used in frameworks and tools that perform bytecode manipulation, such as Hibernate, Spring, and AspectJ.
A key motivation behind the Class-File API is to address the long-standing "chicken and egg" dilemma between the ASM library and the JDK, often referred to as the "ASM N and JDK N+1 problem," where N represents the version number. This issue has consistently posed challenges for Java developers and framework maintainers.
This creates a ripple effect across the Java ecosystem:
To illustrate the problem, suppose:
The Class-File API addresses this problem by:
💡 Note: The chicken and egg problem is one of many problems, but the major one is discussed here.
JEP 455 introduces several key enhancements to Java, especially in how primitive types are handled within patterns, <span class="pink">instanceof</span>, and <span class="pink">switch</span> statements. These changes aim to make the language more consistent and expressive by incorporating primitive types into various contexts.
Enhanced <span class="pink">instanceof
</span>
:
instanceof</span>
was limited to reference types and could not be directly applied to primitive types. This limitation meant you often needed to perform manual checks and typecasting.instanceof</span>
operator to work with primitive types. This enhancement allows you to use <span class="pink">instanceof</span>
to check if a primitive value fits within a certain type (e.g., checking if an <span class="pink">int</span>
value fits within a <span class="pink">byte</span>
). This makes type checking more precise and reduces the risk of errors associated with manual casting.
Extended <span class="pink">switch</span>:
switch</span>
statements were limited in the types they could handle. They could work with <span class="pink">int
, char
, enum</span>
, and <span class="pink">String</pan>
, but lacked support for other primitive types like <span class="pink">long
, float
, double</span>
, and <span class="pink">boolean</span>
directly in the switch
cases. This limitation often required workarounds or less readable code when dealing with these types.switch</span>
statement to support all primitive types, including <span class="pink">long
, float
, double</span>
, and <span class="pink">boolean</span>
. This enhancement allows you to use <span class="pink">switch</span>
with a broader range of types. You can now write cleaner and more expressive <span class="pink">switch</span>
statements that handle various primitive types directly.
JEP455InstanceofDemo.java
Explanation: The above code is a simple demo of how Java SE 23 handles primitive type patterns. In the <span class="pink">JEP455InstanceofDemo</span> class, the <span class="pink">main</span> method checks if an integer value, <span class="pink">number</span>, fits into smaller primitive types like <span class="pink">byte</span> or <span class="pink">short</span> using the <span class="pink">instanceof</span> operator. It prints the value if it fits or indicates it’s an <span class="pink">int</span> otherwise.
Before JDK 23, checking if an <span class="pink">int</span> could fit into a smaller type required manual casting and additional logic, making the code more complex and less readable. JDK 23’s introduction of primitive type patterns streamlines this by allowing direct use of <span class="pink">instanceof</span> with primitive types.
Compiling and Running the Java File:
Output: Number fits in a byte: 42
Note: I used the <span class="pink">--enable-preview</span> flag for compiling and running the program. This flag enables and tests preview features in Java that are new and not yet finalized, allowing both the compiler and JVM to recognize and handle these experimental features during their processes.
JEP455SwitchDemo.java
Explanation:
The above code uses a switch statement to handle <span class="pink">float</span>
values, which wasn’t allowed before. It checks if the <span class="pink">floatValue</span>
matches specific values or patterns and prints a message accordingly.
Compiling and Running the Java File:
Output: Pi
💡 This JEP aims to make Java easier for beginners by allowing students to write their first programs without needing to learn features meant for more advanced, larger projects.
Main.java
Explanation for the above code:
We haven’t defined a class, and we haven’t even passed the <span class="pink">String[] args</span> as an argument in the main method, but still, this program runs.
Output:
Previously known as Statements Before Super (JEP 447) in JDK 22, JEP 482 introduces new flexibility in constructor code.
What’s New:
super()</span>
. This often led to using static methods or blocks for tasks like validation before invoking the superclass constructor.super()</span>.
Key Update from JEP 447 to JEP 482:
In the given code below, the <span class="pink">Dog</span>
constructor attempts to set the <span class="pink">bark</span>
field before calling <span class="pink">super()</span>
, which causes an error in Java prior to JEP 482. This is because subclass fields cannot be initialized before the superclass constructor executes. With JEP 482, this restriction is lifted, allowing such initialization.
Main.java
above code explanation:
Before JEP 482:
Dog</span>
constructor tries to initialize <span class="pink">bark</span>
before calling <span class="pink">super()</span>
.With JEP 482:
super()</span>
) is called.bark="Woof"</span>
before calling <span class="pink">super()</span>
.bark</span>
field is initialized properly before the <span class="pink">Animal</span>
constructor executes.makeSound()</span>
to use the initialized value, resulting in the output:
Running the code prior to this feature would result in:
Output Without Enabling Preview Mode in JDK 23: Initializing Variables Before <span class="pink">super()</span>
Output : Enabling Preview Features in JDK 23
Before diving into the Z Garbage Collector (ZGC) and its new default Generational Mode, let’s first get a quick overview of Java’s garbage collection process and the different types of garbage collectors available in Java.
Garbage Collection (GC) is Java's built-in memory management system that automatically frees up memory by identifying and removing objects no longer in use. It plays a key role in keeping your application running smoothly by managing memory efficiently and preventing memory leaks, so developers don't have to do it manually.
Over time, Java has introduced several garbage collectors to cater to different needs, depending on the application type and memory requirements. Here are the four main garbage collectors in Java today:
Understanding Pause Time in Garbage Collection
Pause time is the period when your application stops running because the system is cleaning up unused memory. If these pauses are too long, your app might feel slow or unresponsive, which can be frustrating for users. Minimizing pause time is important to keep your app running smoothly.
Among Java's garbage collectors, ZGC (Z Garbage Collector) is known for having the least pause time. It’s designed to keep pauses to just a few milliseconds, even with large amounts of memory. This makes it ideal for applications where minimal interruptions and smooth performance are critical.
Important:
💡 By default, Java uses the G1 Garbage Collector unless another option is specified. However, if you choose to enable the Z Garbage Collector (ZGC) over the default G1 garbage collector using the flag <span class="pink">-XX:+UseZGC</span>
, starting from JDK 23 (with JEP 474), ZGC will automatically operate in Generational mode. While the non-generational version of ZGC is still supported, it is likely to be deprecated in the future. If you prefer to continue using the non-generational ZGC, you can explicitly opt out of Generational mode by including the flag <span class="pink">-XX:+UseZGC -XX:-ZGenerational</span>
.
ZGC (Z Garbage Collector) is a highly scalable, low-latency garbage collector introduced in Java 11 as an experimental feature and became production-ready in Java 15. Its primary goal is to handle large heaps (even multi-terabyte heaps) with minimal impact on application performance, particularly focusing on keeping garbage collection (GC) pause times consistently short—typically in the range of sub-milliseconds.
Before the introduction of Generational Mode in JDK 21, the Z Garbage Collector (ZGC) used what’s known as a Non-Generational Mode. In simple terms, ZGC treated all objects the same, regardless of how long they existed in memory. Whether an object was created just a moment ago or had been around for a while, ZGC didn't differentiate between them when deciding what to clean up. This method worked fine for keeping pause times very low, but it wasn’t the most efficient for memory management, especially when it came to dealing with short-lived objects.
Now, let’s talk about Generational Mode. This concept comes from the idea that most objects in a program are either short-lived or long-lived:
In Generational Mode, the garbage collector splits the memory into two main parts:
It specifies that Z Garbage Collector will now default to Generational Mode if selected, with the non-generational mode going to be deprecated. This is a confirmed feature in JDK 23, meaning from JDK 23 onward, you no longer need to use flags to enable ZGC's generational mode; it's the default setting.
Generational mode helps in two major ways:
Strengths:
Trade-offs:
Official Note: We proposed Stream Gatherers as a preview feature in JEP 461 and delivered it in JDK 22. We here propose to re-preview the API in JDK 23, without change, in order to gain additional experience and feedback.
An extensible API for intermediate java.util.stream operations.
Similar to how the Collectors API provides a generic way to define terminal operations, the Gatherer API offers a generic approach for creating intermediate operations.
Note that the Collector's design has a significant influence on the Gatherer interface.
Stream Pipeline:
A stream pipeline in Java is a sequence of steps that processes a collection of data. It's similar to a factory assembly line where each step modifies or filters the data until it reaches its final form. The pipeline starts with a source (like a list), passes through a series of intermediate operations (like sorting or filtering), and ends with a terminal operation that either produces a single result or triggers some actions.
Understanding through a basic example:
Imagine you have a list of numbers and you want to find the sum of all even numbers greater than 10. Here’s how you would set up a stream pipeline in Java to achieve this:
💡 I've talked about the stream pipeline because Stream Gatherers are used to perform intermediary operations within the pipeline.
What is Stream::gather(Gatherer)?
Stream::gather(Gatherer) is a versatile intermediate operation in Java's Stream API that allows developers to process stream elements in highly customizable ways. It is similar to Stream::collect(Collector), which is used for terminal operations, but gather is used for intermediate operations, allowing for continued stream processing.
Purpose and Functionality:
Stream::gather enables the transformation and processing of stream elements through a custom mechanism defined by the developer. This operation can handle complex transformations, including one-to-one, one-to-many, many-to-one, and many-to-many relationships between input and output elements. It supports operations that might require state tracking, conditional processing, or transformations that adapt over time based on previously seen elements.
Gatherer Interface:
Interface Gatherer takes three type parameters :
Methods in this Gatherer interface:
Built-in gatherers
built-in gatherers in the <span class="pink">java.util.stream.Gatherers</span> class:
Understanding through example:
Problem Statement:
We have a List of String i.e. list of Java-related keywords. We have to organize into smaller groups. Each group should have three keywords, and we want to create only the first two groups.
With Stream Gatherers:
Output:
As one can see, the list of keywords is divided into two groups. consisting of three each.
The goal of JEP 471 is to phase out and eventually eliminate the memory-access methods from the <span class="pink">sun.misc.Unsafe</span> class.This is also one of the few final features of JDK 23. These methods, introduced in 2002, allowed Java developers to perform low-level, unsafe operations directly on memory. Though useful, they were never intended for public use and pose significant risks, including the potential for JVM crashes and unpredictable behavior. Therefore, they were not exposed as a standard API.
Many <span class="pink">sun.misc.Unsafe</span>
methods provide direct access to both on-heap and off-heap memory:
Despite the risks, <span class="pink">sun.misc.Unsafe</span> became a favorite over time for library developers seeking greater performance and control than what standard Java APIs could offer, such as atomic operations or advanced off-heap memory management. However, since these methods bypass Java’s safety mechanisms, they introduced potential dangers like JVM crashes and hard-to-debug errors.
Over the years, safer, standard APIs have been introduced to replace these operations.
java.lang.invoke.VarHandle
, introduced in JDK 9 (JEP 193), provides methods to safely and efficiently manipulate on-heap memory, i.e., fields of objects, static fields of classes, and elements of arrays.java.lang.foreign.MemorySegment
, introduced in JDK 22 in Foreign Function and Memory API (JEP 454), provides methods to safely and efficiently access off-heap memory, sometimes in cooperation with <span class="pink">VarHandle</span>
.
The goal is to encourage developers to transition from <span class="pink">Unsafe</span> to these safer, supported APIs, improving compatibility with future JDK versions while reducing the risks of crashes and undefined behavior.
The process will span several JDK releases, beginning with JDK 23. Initially, using these <span class="pink">Unsafe</span>
methods will generate warnings. In later phases, they will trigger runtime exceptions, and eventually, the methods will be fully removed.
In the upcoming release of Java Development Kit (JDK) 23, the highly anticipated String Templates feature has been removed from the list of JEPs and is not even included as a preview feature this time.
String Templates were intended to provide a more concise and readable way of working with string interpolation in Java, but after extensive debate and feedback from the Java community, the language designers have decided to remove it from JDK 23.
String templates, which were introduced as a preview feature in JDKs 21 and 22, were designed to make it easier and safer to embed variables in structured languages like SQL, HTML, and JSON. However, they were surprisingly withdrawn before JDK 23's release.
While many developers would prefer a straightforward string interpolation feature, the primary goal is to address security concerns, such as injection attacks, which remain a significant issue in the industry. Therefore, the new proposal will likely focus on making the secure option more elegant, rather than just making string concatenation simpler.
String Templates were first proposed as part of the Java Improvement Proposal (JEP) 378, which aimed to introduce a new syntax for string interpolation in Java. The key motivation was to provide a more ergonomic and expressive alternative to the traditional string concatenation and <span class="pink">String.format()</span> approaches.
The syntax for string templates looked like this:
The above code uses Java’s String Templates with the <span class="pink">STR</span> processor to interpolate values into a string. The placeholders <span class="pink">\{name}</span> and <span class="pink">\{age}</span> are dynamically replaced with the values of <span class="pink">name</span> and <span class="pink">age</span> during runtime.
Template Processors are at the heart of Java's String Template feature introduced in Java 21 (in preview). A template processor is responsible for converting a String Template (which contains both static text and embedded dynamic values) into a final result, which may be a <span class="pink">String</span>
or any other type.
Introduced as part of the Java 21 Preview feature, String Templates extend beyond basic string interpolation, providing a more robust and secure way to handle dynamic values in strings. Template Processors facilitate this conversion process.
StringTemplate.Processor</span>
interface..
</span>
):
process()
</span>
MethodThe core of the Template Processor's functionality lies in the <span class="pink">process()
</span>
method. This method is responsible for transforming a <span class="pink">StringTemplate</span>
into any desired output type. Its signature is general, allowing flexibility in the return type and making it highly versatile.
<span class="pink">R</span> : The return type of the processor (not restricted to <span class="pink">String</span>). The method can return any type, enabling the transformation of a <span class="pink">StringTemplate</span> into more complex objects like JSON, XML, or even custom types.
<span class="pink">E</span>: The exception type that can be thrown during processing.
<span class="pink">process()</span>: This method takes a <span class="pink">StringTemplate</span> as input and processes it, returning the final result in the form of <span class="pink">R</span>. The actual transformation logic happens inside this method, where the fragments (static text) and values (dynamic parts) of the template are combined.
Java provides some inbuilt template processors named STR, FMT, and RAW to process string template expressions.
\{}</span>
) with actual values at runtime.%s</span>
,<span class="pink"> %d</span>
) in addition to interpolation, allowing for more advanced string formatting. It can be used to generate formatted reports.RAW</span>
processor doesn't return a final string. It doesn't automatically process the string template like the STR template processors. Instead, it returns a <span class="pink">StringTemplate</span>
object, from which you can extract the fragments (static text) and values (dynamic parts) for further manipulation.
You can create your own Template Processor by implementing the <span class="pink">Processor</span> interface, as shown below:
The process method could return anything depending on the logic you write.
The preview of string templates in JDK 21 and 22 aimed to improve the security and readability of combining strings with runtime-computed values.
It allowed developers to embed variables within strings, which were then processed by template processors.
However, the feature faced criticism, particularly for its use of the<span class="pink">\{}</span> syntax instead of the more familiar <span class="pink">${}</span> syntax seen in other languages like JavaScript, TypeScript, and Groovy. Additionally, the special syntax for processor invocations (<span class="pink">$processor.$template</span>) was seen as unnecessary syntax sugar, complicating the feature.
Practical use of string templates also highlighted several limitations:
One of the most debated aspects of the original string template proposal was the use of the <span class="pink">\{}</span> syntax instead of the dollar sign (<span class="pink">$</span>) seen in other languages like JS. While familiarity with<span class="pink">$</span> might seem like a good reason to adopt it, the arguments against it are stronger. For instance, using <span class="pink">$</span> as a special character in string templates would require it to be escaped when appearing as-is, adding complexity. On the other hand, <span class="pink">\{}</span> is already an illegal character sequence in non-template strings, meaning it doesn't require escaping and won't appear in regular strings, making it a better choice.
In the context of programming, escaping refers to the process of using a special character (often a backslash <span class="pink">\</span>) to indicate that the following character should be treated differently than it normally would be. Specifically, in strings, some characters (like the dollar sign<span class="pink">$</span> or quotes <span class="pink">"</span>/<span class="pink">'</span>) have special meanings, and to include these characters as literal text, they need to be "escaped."
For example, in many programming languages, <span class="pink">$</span> is used as part of variable interpolation in strings. If you want to include an actual dollar sign in the string (instead of it being interpreted as part of a variable), you need to escape it by adding a backslash in front of it, like so: <span class="pink">\$</span>.
So in the case of the Dollar Sign Debate, if Java had chosen to use <span class="pink">$</span> for string interpolation (like in JavaScript), then any time a literal dollar sign appeared in a string (for example, when talking about money like <span class="pink">$100</span>), it would need to be escaped as <span class="pink">\$</span>. This would make writing such strings more cumbersome and prone to errors.
By using the <span class="pink">\{}</span> syntax in Java's string templates instead, there is no need to escape the dollar sign because <span class="pink">\{}</span> is an illegal character sequence in regular strings, making it easier and more intuitive to work with.
Elegance and Open Issues
The STR syntax (<span class="pink">STR."text \{variable}"</span>) in the original proposal was criticized for being cumbersome, and while the new proposal may remove this syntax, it won't necessarily result in a shorter or simpler solution. The new design will likely involve creating string templates as a new kind of literal, possibly using backticks as delimiters, but this remains speculative.
There are still many open questions about the future of string templates in Java:
The new proposal for string templates will likely prioritize security over simplicity, and the <span class="pink">\{}</span> syntax is expected to remain.
Regular method calls will likely take place of the special syntax for processor invocations, which means that the template and processing code are no longer required to be adjacent.
💡Each feature has a lot to offer, and covering every detail in a single article isn't possible. If any of them catch your interest, feel free to explore further.
Cheers!
Happy Coding.
JEP 455: Primitive Types in Patterns, instanceof, and switch...
JEP 466: Class-File API (Second Preview)
Java A Classfile API for the JDK #JVMLS
JEP 473: Stream Gatherers (Second Preview)
JEP 473: Stream Gatherers (Second Preview)
JEP 482: Flexible Constructor Bodies (Second Preview)
JEP 471: Deprecate the Memory-Access Methods in sun.misc.Uns...
JEP 474: ZGC: Generational Mode by Default
Update on String Templates (JEP 459)
Oracle Help Center Java Language Updates