After announcing a 6-month release cycle for JDK updates from JDK 11, Java is changing more rapidly than ever. The latest version, Java 22, was released on March 19, 2024. In this article ,we are going to discuss the updates in Java 22.
This year,12 major JDK Enhancement Proposals (JEPs) has included in Java 22:
JEP is a system created by Oracle Corporation for gathering suggestions on how to improve the Java Development Kit and OpenJDK.
These JEPs are divided into three categories based on features:
In Java 22, of the 12 JEPs, 4 are standard features, 7 are in preview, and 1 is an incubator feature.
Understanding some key terms:
In this article, we will look closely at each of the updates and what problem it solves.
In Java programming, there are instances where we need to declare or use variables that don’t actually need a name or specific identification because they are only used temporarily and their individual identity isn't important. This is commonly seen in situations like exception handling and lambda expressions. rephrase in more easy and understandable way.
💡 instead of those unnamed variable, underscore ( _ ) is used.
In exception handling, we often catch exceptions that we need to handle, but we might not need to do anything with the exception object itself except maybe logging it or simply catching it to prevent the program from crashing. In such cases, although a variable (the exception object) is declared, it might not be explicitly named or used.
Understanding through examples:
Handling Exceptions Using Named Variable(exception)
Handling Exceptions Using Unnamed Variable(_)
Output: Not a number
In the context of lambda expressions, sometimes parameters are included in the signature because of interface requirements but are not actually used in the body of the lambda.
Iterating Over a List Using Named Variable(integer)
Iterating Over a List Using Unnamed Variable(_)
Output:
This is an integer
This is an integer
This is an integer
This is an integer
String Templates now in its 2nd preview phase in Java 22 provide a more robust and flexible way to handle string interpolation, which has been a much-requested feature from the developer community. Anyone having the basic knowledge of Javascript must know about Template Strings and how cool this feature is. It is almost similar to that .
Prior to this, Java developers had to use either string concatenation or <span class="pink">String.format()</span>
for dynamic string creation, which could be cumbersome and error-prone. There is always a chance when building a complex string using concatenation with <span class="pink">+</span> operator ,that you will miss some spaces or some formatting issues arises. to tackle this string templates have been introduced.
Note: We're not getting into detail about String Templates because in the official Java 22 presentation, it has been mentioned clearly that this feature is going to be changed completely in the next Java update. So, it’s best not to spend too much time on it right now.
But for just a basic understanding of how string template works, a code is given below:
Classic way using String Concatenation:
Classic way using <span class="pink">String.format()</span>
:
New way using String Templates:
Explanation of the above code:
The <span class="pink">STR</span>
keyword and the <span class="pink">\{}</span>
syntax are part of Java's new String Templates feature. This allows you to easily include variables like <span class="pink">user</span>
and <span class="pink">points</span>
directly in the string. The <span class="pink">STR.</span>
indicates the start of a string template, and the <span class="pink">\{variableName}</span>
placeholders let you insert variable values right into the text. This makes the code cleaner and easier to read, especially for strings that contain several dynamic parts.
Output:
Hello, Gaurav! You have 98 points.
💡 Good Part: If you don’t like the syntax of String Template, you can write one for your own. But as we have discussed earlier,There is a high probability that String Template will be undergoing a major revamp, so we will not discuss it more further.
super(...)
</span>
(JEP 447)
In Java 22, it is now possible to execute statements before calling <span class="pink">super()</span>
in a constructor.
Briefing:
In earlier versions of Java, you were not allowed to execute any statements before calling <span class="pink">super()</span>
in a constructor of a derived class. This restriction was particularly restrictive.
Suppose you want to validate arguments being passed up to the superclass constructor. The common solution was to create static methods that would check the values before they were passed to <span class="pink">super()</span>
. This method worked but often led to the addition of a static method or block just to check something.
With the introduction of Java 22, this constraint has been relaxed. The new feature, "Statements before super()", allows developers to include validation or other logic directly in the constructor of a derived class, right before calling <span class="pink">super()</span>
. This makes the code cleaner and more straightforward, eliminating the need for separate static methods just for argument validation or any other tasks which is required before calling <span class="pink">super()</span>
.
Code with no statement before super():
Main.java
Explanation for the above code:
This Java code defines a <span class="pink">Car</span>
class that extends a <span class="pink">Vehicle</span>
class, ensuring every car has exactly 4 wheels by checking in a method called <span class="pink">validateWheels</span>
. If a car is created with any number of wheels other than 4, the program will throw an error. The main method creates a car, checks the number of wheels, and displays the vehicle type and wheel count, or prints an error message if the wheel count is incorrect.
Code with statement written before super():
Main.java
Output:
Explanation for the above code:
In this Java code, the <span class="pink">Car</span>
class constructor demonstrates the Java 22 preview feature that allows executing certain statements before calling the superclass constructor <span class="pink">super()</span>
. Specifically, it validates the number of wheels for a car using a static method <span class="pink">validateWheels</span>
before passing the validated number to <span class="pink">super()</span>
, ensuring that all cars created by this class have exactly four wheels.
💡 Note: You cannot execute any statements involving instance variables or methods from the derived class before calling <span class="pink">super()</span>
. If you do, your code will not compile because it attempts to access properties of the derived class before the superclass has been properly initialized.
→ An example of code won’t compile if you execute this code because i have removed <span class="pink">static</span>
keyword from <span class="pink">validateWheels</span>
method.
Main.java
Java 22 introduces the ability to launch applications that consist of multiple source code files directly, without explicit compilation.
Problem it solves: This feature simplifies the execution and distribution of small to medium scale Java programs, making Java more script-friendly and improving its usability in educational and rapid prototyping scenarios.
Usage example:
Greet.java
Hello.java
In Java versions prior to JDK 22, attempting to directly compile and execute a Java file that depends on other files, such as classes defined in separate files within a directory, often resulted in a compilation error if not handled correctly. For example, if you had two classes within a folder named <span class="pink">multifile</span>
—one class defined in a file called <span class="pink">Greet</span>
with a static method, and another class in a file called <span class="pink">Hello</span>
that calls this static method from <span class="pink">Greet</span>
—direct execution of the <span class="pink">Hello</span>
file without compiling <span class="pink">Greet</span>
first would lead to errors.
However, starting with Java 22, improvements have been made that allow you to compile and execute multiple interdependent files together using a single command. This enhancement simplifies the process, enabling direct execution without manually managing the compilation order of the dependent files.
With Java 21:
With Java 22:
main
method structure, which typically requires <span class="pink">String[] args</span>
as an argument, is no longer necessary.
Understanding through an example:
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 using PowerShell:
Explanation:
When we first tried running the <span class="pink">Main.java</span>
file, we ran into an issue because the feature we were using, called "Implicitly Declared Classes," is still in its preview phase and isn't turned on by default. The error message was clear, instructing us to enable preview features to continue.
We re-ran the command but this time included the <span class="pink">--enable-preview</span>
option and also specified <span class="pink">--source 22</span>
to ensure the source compatibility with Java 22. With these adjustments, everything worked perfectly, and we got the output:
Output:
This was just to demonstrate how you can utilize Java's preview features, which aren't part of the standard language toolkit yet but can be used for testing and exploring if you explicitly enable them in the Java runtime environment. It's a great way to explore the latest updates without waiting for them to become part of the official release.
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: The design of the <span class="pink">Gatherer</span>
interface is heavily influenced by the design of <span class="pink">Collector</span>
.
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:
Each feature has a lot to offer and we can’t cover everything in great detail. Dive in if you find any of them intriguing.
Regional Pinning has now reached its final phase, making it production-ready and a standard feature in Java 22.
About G1→Java Default Garbage Collector:
💡 The G1 (Garbage-First) is a type of garbage collector used in Java, designed to manage and free up memory in a computer efficiently. It is called "Garbage-First" because it prioritizes and quickly cleans areas with the most garbage, or unused data, first. This helps improve the performance of Java applications by ensuring they have the memory resources they need without delay. G1 is often set as the default garbage collector because it works well across different types of applications, balancing memory cleanup with running time.
Understanding with an Analogy:
💡 Imagine your computer's memory as a desk with lots of papers (data) scattered around. Over time, some papers are no longer needed and just take up space. G1 works like an efficient office worker who quickly sorts through the papers, recycling the ones you don’t need anymore to make room for new ones. This helps keep your computer running smoothly by efficiently organizing memory without slowing down your work.
Briefing:
There are many libraries written in C, C++, and other languages that are extremely useful, yet they cannot be accessed directly from Java applications. To bridge this gap, we rely on JNI (Java Native Interface), a crucial component that allows Java applications to both call and be called by native applications and libraries. JNI provides functions such as <span class="pink">GetPrimitiveArrayCritical</span>
to obtain and <span class="pink">ReleasePrimitiveArrayCritical</span>
to release direct pointers to Java objects.
These functions must be carefully used in pairs: first obtaining a pointer to an object, and then, after using the object, releasing the pointer.The code executed between obtaining and releasing these pointers is considered a critical region, during which the JVM must ensure that the GC does not move the associated Java object.
In these critical regions, the JVM is required to ensure that the garbage collector (GC) does not move the associated Java objects, maintaining the integrity and stability required for native operations.
Problem History:
Historically, the G1 garbage collector, the default choice in Java, would entirely disable garbage collection during these critical periods. While this method ensures the stability of Java objects during native calls, it comes with several drawbacks. The stopping of GC activities can lead to significant latency issues, thread stalling, and in some cases, it might even push the system into out-of-memory conditions, potentially causing premature shutdowns of the JVM.
Solution:
Previously, we had to stop all garbage collection if a critical object was being used, which could slow things down. Now, we don't need to stop everything. Instead, the garbage collector simply avoids the areas with critical objects and continues cleaning up the rest of the memory. This keeps the application running more smoothly.
Addressing these challenges, JEP 423 introduces an innovative solution by implementing region pinning within the G1 garbage collector. This enhancement allows specific memory regions containing critical objects to be pinned, thus exempt from being moved during garbage collection cycles. This approach ensures that garbage collection can proceed in other parts of the heap without disrupting the integrity of the objects involved in native interactions. The result is a more efficient GC process, reducing latency, preventing thread stalling, and avoiding out-of-memory errors, thereby enhancing the reliability and performance of Java applications interacting with unmanaged languages.
How it works?
JEP 423 has changed the way the G1 garbage collector works by introducing region pinning. Instead of stopping the entire garbage collection process, it now allows specific areas of memory that hold important objects to be "pinned" or locked down during cleaning. This happens by keeping track of how many critical objects are in each area. The count goes up when an object is accessed and down when it's released. If there's even one critical object left, that area won't be touched during garbage collection. This approach keeps those important objects in place, avoiding the need to stop all garbage collection, which helps reduce delays significantly.
💡 Java 22 offers a cool new alternative for working with libraries from other languages, reducing our dependency on JNI and its associated overhead. This alternative is JEP 454, the Foreign Function and Memory API, which we'll dive into later.
Project Panama is an initiative by the Java community to improve the connection between the Java Virtual Machine (JVM) and native code, which is often written in languages like C and C++. Foregin Function and Memory API comes under the project Panama to make the interaction more suiutable between Java and Non-Java API.
Briefing:
JNI(Java Native Interface) has long been the sole method for accessing native libraries from Java, but its complex and fragile programming model makes it cumbersome for large-scale use. However, this is no longer the case: JDK 22 introduces the Foreign Function & Memory API, offering a safe, contemporary, and efficient approach to interfacing with foreign memory and code directly from Java.
Native Code:
Native code refers to software or program instructions that are written to run directly on a specific processor architecture and its operating system.
It’s written to work on one particular type of computer or device. This is different from general code written in languages like Java or Python, which are more like universal languages that need a translator (like a Java Virtual Machine for Java) to help the computer understand what to do.
Understanding Native Code through Analogy:
💡 Imagine you’re traveling and you speak directly in the local language of the place you’re visiting — that's like native code. It's fast and efficient because there’s no need for translation. But if you're using a universal language and rely on a translator, it might take more time to communicate, just like how Java works on different devices.
Native code is really good for tasks where speed and efficiency are super important, such as video games or programs that handle lots of data quickly. It’s like having a direct conversation with the computer’s hardware without any middle steps.
Important:
Note: We can't explore every feature in great detail or address all the features in a single article. Therefore, we won't delve deeply into the FFM API and other features here. In our next article, we will aim to cover these topics more thoroughly.
Cheers.
Keep coding and have fun!