A list of major Java and JVM features since JDK 17 to 22 - Java22 16x9 removebg preview

Now that organizations are starting to shift to Java 21, let’s take a look at some of the more impactful changes in the newest releases. There have been several noteworthy Java Enhancement Proposals (JEPs) that bring significant advancements to the language.

This is meant as a high-level overview, and I certainly recommend diving deeper into the details to fully understand the benefits and implications of these updates.

Some standouts for me include:

  • Sealed Classes: These provide a powerful way to enhance code clarity, readability, safety, and maintenance. By restricting which classes can extend or implement a particular class or interface, sealed classes allow developers to create more predictable and controlled class hierarchies.
  • Pattern Matching Enhancements: These offer useful new destructuring options, making it easier to extract and manipulate data from complex objects. This can lead to more concise and readable code.
  • Foreign Function and Memory API: This is a treat for anyone who has used the Java Native Interface (JNI) before to access low-level APIs. This new API simplifies the process of interacting with native code and memory, reducing the complexity and potential for errors.

I’ve tried to add examples for all these enhancements below to illustrate how they can be used in practice. I hope that some of you may find it valuable.

There are certainly a plethora of new additions that are pushing the language forward, making Java more robust and versatile for modern software development needs.

In this article, we will cover all Java and JVM features from JDK 17 to 22, including new language features, API changes, security updates, documentation improvements, deprecations, and a look ahead. While there have been many improvements in the past few releases, I am excitedly awaiting the finalization of some JEPs as of this writing.

New language features

JEP-409: Sealed Classes (17)

In older Java versions we were quite restricted in inheritance control where we had to make use of final,or package-private classes. This wasn’t an ideal paradigm since we potentially limited the accessibility of the superclass, and we didn’t make the intended use-case of it clear.

This is where the sealed modifier comes in handy. We can define a sealed interface/classes, which permits certain interfaces/classes to access it. This clearly indicates to us the potential use-cases.

The subclass itself must be final, sealed or non-sealed. This last option indicates that this file itself is open for extension.

So we could for example do:

    public sealed class Shape permits Circle, Rectangle {
        // ...
    }

    public final class Circle extends Shape {
        // ...
    }

    public non-sealed class Rectangle extends Shape {
        // ...
    }
    
    public class SpecialRectangle extends Rectangle {
        // ...
    } 

 There are certainly other advantages, such as in switches which we’ll cover later.

JEP-440: Record patterns (21)

When working with complex nested objects we still had to deconstruct our object. We can now destructure our objects in switch and instanceof.

For example:

        switch (object) {
            case Path(Coordinate2D(int fromX, int fromY), Coordinate2D(int toX, int toY)) -> System.out.printf("From: %d  %d - to: %d  %d%n", fromX, fromY, toX, toY);
            case Path(Coordinate3D(int fromX, int fromY, int fromZ), Coordinate3D(int toX, int toY, int toZ)) -> System.out.printf("From: %d  %d %d - to: %d  %d %d%n", fromX, fromY, fromZ, toX, toY, toZ);
            default ->  System.out.printf("An unknown object: %s%n", object);
        }

JEP-441: Pattern matching for switch (21)

Before Java 21, if we wanted to apply certain conditionality to an object based on its type we swiftly ended up with a length if/else if/else chain. We can now make use of type patterns to match what we’re switching on and also use guarded patterns .

       switch (object) {
            case Coordinate2D c-> System.out.printf("Coordinate: %d  %d%n", c.x(), c.y());
            case String s-> System.out.printf("String: %s%n", s);
            default ->  System.out.printf("An unknown object: %s%n", object);
        }

        switch (o) {
            case Integer i when i > 42 -> "Beyond the meaning of life";
            case Integer i when i == 42 -> "The meaning of life";
            case Integer i when i <= 42 -> "Keep searching";
            default -> "unknown";
        };

JEP 456: Unnamed Variables & Patterns (22)

At times we have to define variables we don’t need, such as certain lambda parameters/exceptions/…
Some of you might recall, that since Java 9 we were no longer allowed to use _ as a variable now.
As of Java 22, we can use it again, and it has a special meaning: it indicates we won’t be using the variable. Hence your IDE will also no longer nag you about unused variables.

        Object coordinate = new Coordinate(10D, 11D);
        if (coordinate instanceof Coordinate(double x, double _)) {
            System.out.println("The abscissa is: " + x);
        }

API changes

JEP-306: Restore Always-Strict Floating-Point Semantics(17)

As of Java 17, the floating-point operations are once again consistently strict. This restores the behaviour as it was before 1.2 when strict and default floating-point modes were introduced.

JEP-382: New macOS Rendering Pipeline(17)

This JEP adds a new Java 2D internal rendering pipeline that makes use of the Apple Metal API as an alternative to the deprecated Apple OpenGL API. 

JEP-400: UTF-8 by Default (18)

We’ve likely all run into some encoding issues, especially if there are team members with different operating systems. For example in West-EU Windows-1252 whereas UTF-8 is the default in most Linux devices

        String specialText = "àèìòù";
        System.out.println(specialText);
        System.out.println(System.getProperty("file.encoding"));
        System.out.println(Charset.defaultCharset() );

JDK-8301226 – Clamp method for java.lang.(Strict)Math (21)

I’ll admit, this might not seem like a huge change. But one frequently has to constrain a number within a range.


In older versions, we did:

In older versions, we did:

Now we can do the following:

        Math.clamp(value, min, max)

JEP-439: Generational ZGC

Since Java 17 a lot of the garbage collectors have seen improvements, but the ZGC which is meant to be a scalable low-latency garbage collector has been receiving a lot of attention. In case you’re interested, I can recommend at looking at Episode 24 of the Inside Java Podcast.  

JEP-444: Virtual threads (21)

With this JEP the loom’s coming closer to completion (there are still some JEPs we need like structured concurrency)! 

Historically speaking, when scaling the number of threads is often a bottleneck. They’re limited in number and often have to wait for events. 

To handle this there have been alternative approaches such as CompletableFuture and reactive frameworks, but these generally are a bit challenging to read and write.

Virtual threads themselves scale magnificently as long as you make sure they can be unmounted (not stuck to their carrier during a blocking operation). You can have 100 000s of these with negligible memory consumption. But do keep shared resource consumption in mind!

Creating a virtual thread can be as easy as: 

        Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello world"));

Or we can make use of:

 Executors.newVirtualThreadPerTaskExecutor()

To help troubleshoot the new JDK Flight Recorder events have been added.

In case you want to take a sneak peek at future options: Time for some structured concurrency

JEP-454: Foreign Function & Memory(FFM) API (22)

I think we can all agree that Java Native Interface (JNI) isn’t always the easiest to work with.
Given we now have the FFM API the architects certainly agreed with us.

FFM is part of project Panama and is intended to allow us to efficiently and safely access foreign memory (outside of the Java heap), and easily call functions from libraries with native libraries. 

Compared to JNI:

  • a much lower implementation effort
  • (much) faster than JNI
  • Less error-prone, since thanks to arenas we’ll get exceptions rather than crashing the JVM

For example, let’s use the C strlen function:

       final var numbers = List.of("1", "2", "3");

       Stream.of(Locale.ENGLISH)
                .forEach(locale -> Arrays.stream(ListFormat.Type.values())
                        .forEach(type -> Arrays.stream(ListFormat.Style.values())
                                .forEach(style ->
                                        System.out.println(STR."\{locale} \{type} \{style}: \{
                                                ListFormat.getInstance(locale, type, style).format(numbers)}"
                                        )
                                )
                        )
                );

Which will result in:

en STANDARD FULL: 1, 2, and 3
en STANDARD SHORT: 1, 2, & 3
en STANDARD NARROW: 1, 2, 3
en OR FULL: 1, 2, or 3
en OR SHORT: 1, 2, or 3
en OR NARROW: 1, 2, or 3
en UNIT FULL: 1, 2, 3
en UNIT SHORT: 1, 2, 3
en UNIT NARROW: 1 2 3

Security

JEP-452: Key Encapsulation Mechanism API (21)

This is a new API to leverage a more modern encryption technique to secure symmetric keys using public key cryptography(Key Encapsulation Mechanism KEM)  It aims to enable us to use KEM algorithms such as RSA-KEM, ECIES, etc.. It’s a nice step forward in cryptography and security.

This is needed as attacks are growing ever more sophisticated, and we also want to make use of KEM in higher-level protocols such as TLS.

It consists of three functions:

  • Key par generation function
  • Key encapsulation function
  • Key decapsulation function

JDK-8275252: keystore file

This file is no longer a JKS but PKCS12 file.

Features

JEP-408: Simple web server (18)

In my opinion. this is one of the more undervalued recent additions.

It adds a new jwebserver tool so we can start a simple web server from our shell/terminal.
For example, if we want to start a server serving the files in our tmp directory on port 3000 we can do:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jwebserver --port 3000 --directory ~/tmp

It also adds an API we can use.
We can start a very basic server with this code:

public class SimpleWebServerDemo {
    private static final InetSocketAddress LOOPBACK_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 8080);

    public static void main() {
        Path path = Path.of(new File("src/main/resources").getAbsolutePath());
        final var server = SimpleFileServer.createFileServer(LOOPBACK_ADDRESS, path, SimpleFileServer.OutputLevel.VERBOSE);
        server.start();
    }
}

Which of course is a bit boring. We can programmatically define extra handlers thanks to the new HttpHandlers class.

For example, if we wanted to set up a simple Hello world webserver, which only supports GET we could do:

        var okHandler = HttpHandlers.of(200, Headers.of("X-method", "GET"), "<h1>Hello world</h1>");
        var errHandler = HttpHandlers.of(404, Headers.of("X-method", "*"), "<h1>Only get is supported</h1>");
        var combinedHandler = HttpHandlers.handleOrElse(
                r -> r.getRequestMethod().equals("GET"),
                okHandler,
                errHandler
        );
        var server = HttpServer.create(
                new InetSocketAddress(3000),
                10,
                "/",
                combinedHandler
        );
        server.start();

 

In case you want to experiment with the API further, there are fun things to be done in conjunction with JIMFS (such as a simple in-memory file server).

JEP-423: Region pinning for G1 (22)

This is a useful interim measure for applications that haven’t migrated to the Foreign Function & Memory API yet. It allows G1 to still perform garbage collection on regions with objects that are being used by the Java Native Interface by pinning those objects in that specific region.

JEP-458: multi-file source-code programs (22)

This JEP is geared towards smoothening out the onboarding process and so that people less quickly need to resort to tools such as Maven or Gradle.

Let’s say we have a small application that consists of a Main and Utility class. Previously when we did:

       jwebserver --port 3000 --directory ~/tmp

 It also adds an API we can use.
We can start a very basic server with this code:

public class SimpleWebServerDemo {
    private static final InetSocketAddress LOOPBACK_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 8080);

    public static void main() {
        Path path = Path.of(new File("src/main/resources").getAbsolutePath());
        final var server = SimpleFileServer.createFileServer(LOOPBACK_ADDRESS, path, SimpleFileServer.OutputLevel.VERBOSE);
        server.start();
    }
}

Which of course is a bit boring. We can programmatically define extra handlers thanks to the new HttpHandlers class.

For example, if we wanted to set up a simple Hello world webserver, which only supports GET we could do:

        var okHandler = HttpHandlers.of(200, Headers.of("X-method", "GET"), "<h1>Hello world</h1>");
        var errHandler = HttpHandlers.of(404, Headers.of("X-method", "*"), "<h1>Only get is supported</h1>");
        var combinedHandler = HttpHandlers.handleOrElse(
                r -> r.getRequestMethod().equals("GET"),
                okHandler,
                errHandler
        );
        var server = HttpServer.create(
                new InetSocketAddress(3000),
                10,
                "/",
                combinedHandler
        );
        server.start();

In case you want to experiment with the API further, there are fun things to be done in conjunction with JIMFS (such as a simple in-memory file server).

JEP-423: Region pinning for G1 (22)

This is a useful interim measure for applications that haven’t migrated to the Foreign Function & Memory API yet. It allows G1 to still perform garbage collection on regions with objects that are being used by the Java Native Interface by pinning those objects in that specific region.

JEP-458: multi-file source-code programs (22)

This JEP is geared towards smoothening out the onboarding process and so that people less quickly need to resort to tools such as Maven or Gradle.

Let’s say we have a small application that consists of a Main and Utility class. Previously when we did:

Java Main.java

We had to be certain that the Utility class had already been compiled. This is no longer needed. If the Main program references the Utility class, the launcher will automatically compile Utility.java for us as well.

Documentation

JEP-413: Javadoc code snippets (18)

The @Snippet tag allows us to easily add multi-line code with styling, and the possible inclusion of external snippets.

/**
 * A sample use for this class is:
 * {@snippet :
 * var combinedHandler = HttpHandlers.handleOrElse(
 *     r -> r.getRequestMethod().equals("GET"),
 *     okHandler,
 *      errHandler
 * );
 * }
 */

Deprecations

There are certain deprecations to keep in mind:

  • Deprecate the applet API for removal (17) 
  • deprecate the security manager for removal (17)
  • deprecate Finalization for removal (18)
  • deprecate Thread.stop for removal (18)
  • JARs signed with SHA-1 algorithms are now restricted by default and treated as if they were unsigned (18)
  • java.net.URL constructors are deprecated (20)
  • Windows 32-bit X86 port has been marked for Removal (21)
  • Preparations are being made to disallow dynamic loading of agents (21)

Lookahead

While there have been a lot of improvements in the past few releases, at the time of writing I am excitedly awaiting the finalization of some JEPs.

Scoped values + Structured concurrency

Currently, I see people struggling with the flow of their threads, and the scoping/availability of data within these.

Scoped values will act as implicit method parameter(s) which are only accessible by methods in the call chain that Java access to the scoped value object can use its data. This will allow us to pass data far down the call stack. This promises to be a lot more opportune than thread-local variables, especially when we have a lot of Virtual Threads.

Structured concurrency will allow us to clearly treat groups of related tasks as a single task, which will allow us to easily stop due to a failure/success, simplify the observability and the reliability (we’ve likely all seen an application with some dangling remnants in case something unexpectedly fails), …

When these two land, it will be much easier to observe (including using toolsets!) what’s going on. The direct readability of the code will improve, the tooling is expanding, …

Module import declarations

Not everyone is quite as keen on explicitly working with modules, but the module system does offer a lot of benefits in my opinion. The new Module Import Declarations (JEP-476) is intended to simplify working with them by:

  • Allowing entire modules to be imported
  • Avoiding multiple type-import-on-demand declaration noise
  • Demanding less knowledge of the package hierarchy 
  • Not requiring developers to modularize their own code when using the module import feature

Editor’s thoughts

In conclusion, we have covered all significant Java and JVM features from JDK 17 to 22, including new language features, API modifications, security enhancements, documentation updates, deprecations, other features and future prospects. While many advancements have been made in recent releases, I am eagerly looking forward to the finalization of several JEPs at the time of writing.

Looking at the current JEPs in incubation/preview status we can certainly expect a lot from upcoming Java releases such as withers, structured concurrency, String templates, … The days of Java merely trodding along are certainly long gone.

Upgrading versions can always offer a lot of changes/nuances and potential (hopefully always positive) performance impacts. So knowing what’s going on in your applications is also important. So please when modernizing your applications please also invest in your observability.

Certain tools like Digma can hook into this, so you can observe the impact while developing, and testing your changes locally.  Download Digma: Here

References

Data-oriented programming – Brian Goetz – an interesting article on how we can make use of records, sealed, and the new switch options to achieve algebraic data types and apply them for Data-Oriented Programming.  

Inside Java podcast 22: Simple Web Server 
Inside java podcast 24: towards generational ZGC! 
Time for some structured concurrency 
Key Encapsulation Mechanisms
Java Almanac – a nice source to see what got added in which version

This article was contributed to the Digma blog by Simon Verhoeven, a Senior Software Engineer and Java Consultant with a particular focus on Cloud quality and Maintainability.

Spread the news:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *