What's new in Java 17
Last updated on November 14, 2022 am
On September 14, 2021 the time had finally come: After the five “intermediate versions” Java 12 to 15, each of which was only maintained for half a year, the current long-term support (LTS) release, Java 17, was released.
Oracle will provide free upgrades for Java 17 for at least five years, i.e. until September 2026 - and extended, paid support until September 2029.
In Java 17, 14 JDK enhancement proposals were implemented. I sorted the changes by relevance to day-to-day programming work. The article begins with language enhancements and changes to the module system. Various extensions of the JDK class library follow, performance improvements, new preview and incubator features, deprecations and deletions and finally other changes that are rarely encountered in day-to-day work.
As always, I have used the English names of the JEPs and other changes. A translation into German would not bring any added value here.
Sealed Classes
The big innovation in Java 17 (besides long-term support) are sealed classes and interfaces.
What sealed classes are, exactly how they work and why we need them, I will explain in a separate article due to the scope of the topic: Sealed Classes in Java
(Sealed classes were first introduced in Java 15 as a preview feature. Three small changes were released in Java 16. JDK Enhancement Proposal 409 declares sealed classes production-ready in Java 17 with no further changes.)
Strongly Encapsulate JDK Internals
The module system ( Project Jigsaw ) was introduced in Java 9, in particular to be able to better modularize code and to increase the security of the Java platform.
Before Java 16: Relaxed Strong Encapsulation
Up until Java 16, this had little impact on existing code, as the JDK developers provided “Relaxed Strong Encapsulation” mode for a transitional period.
This allowed non-public classes and methods of packages in the JDK class library that existed before Java 9 to be accessed via deep reflection without changing the configuration.
The following example extracts the bytes of a string by reading its private field value
:
1 |
|
If we call this program with Java 9 to 15, we get the following output:
1 |
|
We see some warnings, but then we get the desired bytes.
Deep reflection on new packages, on the other hand, was not allowed by default and had to be explicitly allowed on the command line with “–add-opens” since the introduction of the module system.
The following example tries to instantiate the class ConstantDescs
from the package added in Java 12 (i.e. after the introduction of the module system) java.lang.constant
via its private constructor:
1 |
|
The program aborts with the following error message:
1 |
|
In order to make the program runnable, we need to --add-opens
open via the new package for Deep Reflection:
1 |
|
The code then runs error-free and without warnings.
Since Java 16: Default Strong Encapsulation + Optional Relaxed Strong Encapsulation
In Java 16 , the default mode was changed from “Relaxed Strong Encapsulation” to “Strong Encapsulation”. Since then, access to pre-Java 9 packages has also had to be explicitly allowed.
If we run the first example on Java 16 without explicitly allowing access, we get the following error message:
1 |
|
However, Java 16 offered a workaround: You --illegal-access=permit
could switch back to “Relaxed Strong Encapsulation” using the VM option:
1 |
|
Since Java 17: Strong encapsulation only
Per JDK Enhancement Proposal 403 , this option will be removed from Java 17. The VM option --illegal-access
has been warned and inaccessible by String.value
default:
1 |
|
If you want to use Deep Reflection from Java 17, you must now explicitly --add-opens
allow it:
1 |
|
The program is running and we no longer see any warnings - the long transition phase since Java 9 has thus been completed.
Add java.time.InstantSource
The class java.time.Clock
is extremely useful for writing tests that verify time-dependent functionality.
if Clock
e.g. B. is injected into the application classes via dependency injection, you can mock them in tests or Clock.fixed()
set a fixed time for the test execution.
Since Clock
the method getZone()
provides, you always have to think about which specific time zone you Clock
instantiate an object with.
In order to enable alternative, time zone-independent time sources, the interface java.time.InstantSource
from was Clock
extracted in Java 17, which only provides the methods instant()
and for querying the time, which is already implemented as a default method.millis()``millis()
The Timer
class in the following example uses InstantSource
to determine the start and end times of an Runnable
execution and calculate the duration of the execution from that:
1 |
|
In production we can Timer
instantiate using the system clock (although in the absence of alternative implementations we have to InstantSource
worry about the timezone - we’ll use the system’s default timezone):
1 |
|
We can test the measure()
method by InstantSource
mocking its instant()
method to return two fixed values and comparing the return value of measure()
with the difference between these values:
1 |
|
There is no JDK Enhancement Proposal for this extension.
Hex Formatting and Parsing Utility
Until now, we could use – or use – the toHexString()
method of the classes Integer
, Long
, Float
and to output hexadecimal numbers . The following code shows a few examples:Double``String.format()
1 |
|
The code results in the following output:
1 |
|
We could parse hexadecimal numbers with the respective counterparts:
1 |
|
Java 17 provides the new class java.util.HexFormat
that is used to represent and parse hexadecimal numbers via a unified API. HexFormat
supports all primitive integers ( int
, byte
, char
, long
, short
) and byte
arrays – but no floating point numbers.
Here is an example of the conversion to hexadecimal numbers:
1 |
|
The output is:
1 |
|
It is noticeable that the output always comes with leading zeros.
The representation we can z. B. adjust as follows:
1 |
|
ofDelimiter()
Specifies a delimiter for formatting byte arrays.withPrefix()
defines a prefix – but only for byte arrays!withUpperCase()
switches the output to uppercase.
The output is now:
1 |
|
The leading zeros cannot be removed.
We can parse integers as follows:
1 |
|
Corresponding methods for char
, byte
and short
do not exist.
We can parse byte arrays e.g. B. as follows:
1 |
|
There are other methods to B. to parse only a substring. See the HexFormat JavaDoc for full documentation .
There is no JDK Enhancement Proposal for this extension.
Context-Specific Deserialization Filters
Object deserialization poses a significant security risk. Malicious attackers can use the data stream to be deserialized to construct objects, through which they can ultimately execute arbitrary code in arbitrary classes (available on the classpath).
Java 9 introduced deserialization filters, the ability to specify which classes may (or may not) be deserialized.
Previously there were two ways to define deserialization filters:
- per
ObjectInputStream.setObjectInputFilter()
for each deserialization separately, - system-wide uniformly via system property
jdk.serialFilter
or via security property of the same name in the fileconf/security/java.properties
.
For complex applications, especially those with third-party libraries that also contain deserialization code, these variants are not satisfactory. For example, deserialization in third-party code cannot be ObjectInputStream.setObjectInputFilter()
configured via (unless you change their source code), but only globally.
As of Java 17, JDK Enhancement Proposal 415 makes it possible to set context-specific deserialization filters, e.g. B. for a specific thread or based on the call stack for a specific class, module, or third-party library.
The configuration of the filters is not easy and would go beyond the scope of this article. Details can be found in the JEP linked above.
JDK Flight Recorder Event for Deserialization
As of Java 17 it is also possible to monitor the deserialization of objects via the JDK Flight Recorder (JFR).
Deserialization events are disabled by default and must be enabled via the event identifier jdk.Deserialization
in the JFR configuration file (see the article linked below for an example).
If a deserialization filter is activated, the JFR event contains an indication of whether the deserialization was executed or rejected.
For more detailed information and an example, see the article “ Monitoring Deserialization to Improve Application Security “.
The Flight Recorder events for the deserialization are not part of the above JDK Enhancement Proposal; there is also no separate JEP for them.
Enhanced Pseudo-Random Number Generators
Until now, it has been tedious to replace the random number-generating classes Random
and SplittableRandom
in an application (or even to replace them with other algorithms) even though they offer a largely identical set of methods (e.g. nextInt()
, nextDouble()
and stream-generating methods such as ints()
and longs()
).
So far, the class hierarchy looked like this:
Pre-Java 17 Pseudo-Random Number Generators
JDK Enhancement Proposal 356 introduced a framework of inheriting interfaces for the existing and new algorithms in Java 17, so that the concrete algorithms can easily be exchanged in the future:
Java 17 Pseudo-Random Number Generators
The methods common to all random number generators such as nextInt()
and nextDouble()
are RandomGenerator
defined in . If you only need these methods, you should always use this interface in the future.
The framework includes three new types of random number generators:
JumpableGenerator
: provides methods to skip a large number of random numbers (e.g. 2 64 ).LeapableGenerator
: provides methods to skip a very large number of random numbers (e.g. 2 128 ).ArbitrarilyJumpableGenerator
: provides additional methods to skip any number of random numbers.
Also, code duplicated in the existing classes has been eliminated and code has been extracted into non-public abstract classes (not visible in the class diagram) to make it reusable for future random number generator implementations.
RandomGeneratorFactory
New random number generators can be added and instantiated via the Service Provider Interface (SPI) in the future .
Performance
With asynchronous logging, Java 17 brings a long-overdue performance improvement to the Unified JVM logging system introduced in Java 9.
Unified Logging Supports Asynchronous Log Flushing
Asynchronous logging is a feature supported by all Java logging frameworks. The application thread first writes log messages to a queue. A separate I/O thread then routes them to the configured output (console, file, or network).
The application thread does not have to wait for the I/O subsystem to process the message.
As of Java 17, asynchronous logging can also be activated for the JVM itself. This is done using the following VM option:
-Xlog:async
The logging queue is limited to a fixed size. If the application sends more log messages than the I/O thread can process, the queue fills up. She then rejects further messages without comment.
The size of the queue can be adjusted using the following VM option:
-XX:AsyncLogBufferSize=<Bytes>
There is no JDK Enhancement Proposal for this extension.
Preview and Incubator features
Even though Java 17 is a long-term support (LTS) release, it contains preview and incubator features that are expected to reach production maturity in one of the next “intermediate releases”. If you only use LTS releases, you have to wait at least until Java 23 to use these features.
Pattern Matching for switch (Preview)
In Java 16 “Pattern Matching for instanceof” was introduced. This made explicit casts after instanceof
tests superfluous. This allows e.g. B. Code like the following:
1 |
|
With JDK Enhancement Proposal 406 , the check whether an object is an instance of a certain class can also be written as a switch
statement (or expression) in the future.
Pattern matching for switch statements
Here is the example from above rewritten into a switch
statement:
1 |
|
It is noticeable that the default
case must be specified - in this case with an empty code block, since an action should only be carried out for strings and integers.
The code becomes much more readable if we combine case
- and - expressions with a logical “and” (this is then called a “guarded pattern”):if
1 |
|
It is important that a so-called “dominant pattern “ must come after a “dominant pattern “. In the example, the shorter pattern from line 3 “String s” dominates the longer one from line 2.
If we swapped these lines, it would look like this:
1 |
|
In this case, the compiler would criticize line 3 with the following error message:
Label is dominated by a preceding case label ‘String s’
The reason for this is that now every string – no matter what length – is matched by the pattern “String s” (line 2) and doesn’t even get to the second case test (line 3).
Pattern matching for switch expressions
Pattern matching can also be used for switch
expressions ( i.e. switch
with a return value):
1 |
|
The case must default
return a value, otherwise the return value of the switch
expression could be undefined - or, as in the example, throw an exception.
Completeness check with Sealed Classes
Incidentally, when using sealed classes , the compiler can check whether switch
the statement or expression is complete. If that is the case, a default
case is not needed.
This has an advantage that is not immediately apparent: if one day the sealed hierarchy is extended, the compiler will recognize the then incomplete switch
expression and you will be forced to complete it. This saves you from making mistakes that go unnoticed.
“Pattern Matching for switch” will be presented again in Java 18 as a preview feature and will probably be ready for production in Java 19.
Foreign Function & Memory API (Incubator)
Since Java 1.1, the Java Native Interface (JNI) offers the possibility of calling native C code from within Java. However, JNI is extremely complex to implement and slow to run.
To create a JNI replacement, Project Panama was created. Concrete goals of this project are a) to simplify the effort of the implementation (90% of the work is to be eliminated) and b) to improve the performance (by a factor of 4 to 5).
In the last three Java versions, two new APIs have been introduced, each in the incubator stage:
- The Foreign Memory Access API (introduced in Java 14 , refined in Java 15 and Java 16 ),
- The Foreign Linker API (introduced in Java 16 ).
JDK Enhancement Proposal 412 combined both APIs in Java 17 into the “Foreign Function & Memory API”.
This is still in the incubator stage, so it may still be subject to significant changes. I will introduce the new API in the Java 19 article when it reaches the preview stage.
Vector API (Second Incubator)
As already described in the article about Java 16 , the Vector API is not about the old java.util.Vector
class, but about mapping mathematical vector calculations to modern CPU architectures with single-instruction-multiple-data (SIMD) support.
JDK Enhancement Proposal 414 improved performance and extended the API, e.g. B. to support Character
(previously , Byte
, Short
, Integer
, Long
and Float
were Double
supported).
Because features in Incubator status can still undergo significant changes, I’ll announce the feature when it reaches Preview status.
Deprecations and Deletion
In Java 17, some obsolete features were again marked as “deprecated for removal” or completely removed.
Deprecate the Applet API for Removal
Java applets are no longer supported by any modern web browser and were already marked as “deprecated” in Java 9.
JDK Enhancement Proposal 398 marks them as “deprecated for removal” in Java 17. This means that they will be completely removed in one of the next releases.
Deprecate the Security Manager for Removal
The Security Manager has been part of the platform since Java 1.0 and was primarily intended to protect the user’s computer and data from downloaded Java applets. These were launched in a sandbox in which the Security Manager generally denied access to resources such as the file system or the network.
As described in the previous section, Java applets have been marked as “deprecated for removal”; this aspect of the Security Manager will no longer be relevant.
In addition to the browser sandbox, which generally denied access to resources , the Security Manager was also able to secure server applications using policy files. Elasticsearch and Tomcat are examples of this.
However, it’s not of much interest anymore because it’s complicated to configure and security can now be better implemented via the Java modular system or isolation through containerization.
In addition, the Security Manager represents a not inconsiderable amount of maintenance work. For all extensions to the Java class library, it must be evaluated to what extent they need to be secured using the Security Manager.
For these reasons, JDK Enhancement Proposal 411 in Java 17 classified the Security Manager as “deprecated for removal”.
It is not yet certain when the Security Manager will be completely removed. In Java 18 it will still be included.
Remove RMI Activation
Remote Method Invocation is a technology for invoking methods on “remote objects”, i.e. objects on another JVM.
RMI Activation allows objects destroyed on the target JVM to be automatically re-instantiated as soon as they are accessed. This is intended to eliminate the need for error handling on the client side.
However, RMI activation is relatively complex and results in ongoing maintenance costs; it’s also virtually unused, as analysis of open source projects and forums like StackOverflow have shown.
For this reason, RMI Activation was marked as “deprecated” in Java 15 and completely removed in Java 17 by JDK Enhancement Proposal 407 .
Remove the Experimental AOT and JIT Compiler
In Java 9, Graal was added to the JDK as an experimental ahead-of-time (AOT) compiler. In Java 10 , Graal was also made available as a just-in-time (JIT) compiler.
However, both features have been little used since then. Because the maintenance overhead is significant, Graal has been removed in the JDK 16 builds released by Oracle. Since nobody complained about this, both AOT and JIT compilers have been completely removed in Java 17 with JDK Enhancement Proposal 410 .
The Java-Level JVM Compiler Interface (JVMCI) used to connect Graal has not been removed, and Graal itself is also being further developed. To use Graal as an AOT or JIT compiler, you can download the Java distribution GraalVM .
Other changes in Java 17
In this section you will find minor changes to the Java class library that you will not come into contact with on a daily basis. Still, I recommend skimming through them at least once so you know where to look when you need a feature.
New API for Accessing Large Icons
Here is a small Swing application that renders the directory C:\Windows
‘s filesystem icon on Windows:
1 |
|
The icon has a size of 16 x 16 pixels, and there was no way to display a higher resolution icon.
In Java 17 the method was getSystemIcon(File f, int width, int height)
added with which you can specify the size of the icon:
1 |
|
There is no JDK Enhancement Proposal for this extension.
Add support for UserDefinedFileAttributeView on macOS
The following code shows how extended attributes of a file can be written and read:
1 |
|
This functionality has existed since Java 7, but was not previously supported on macOS either. Since Java 17, the function is now also available for macOS.
There is no JDK Enhancement Proposal for this extension.
System Property for Native Character Encoding Name
As of Java 17, the system property “native.encoding” can be used to call up the standard character encoding of the operating system:
1 |
|
On Windows it Cp1252
outputs , on Linux and macOS UTF-8
.
If you call this code with Java 16 or earlier, is null
output.
There is no JDK Enhancement Proposal for this extension.
Restore Always-Strict Floating-Point Semantics
An almost unknown Java keyword is strictfp
. It is used in class definitions to make floating point operations strict within a class. This means that they lead to the same, predictable results on all architectures.
Strict floating-point semantics was the default behavior prior to Java 1.2 (that is, more than 20 years ago).
As of Java 1.2, the “standard floating point semantics” was used by default, which could lead to slightly different results depending on the processor architecture. On the other hand, it performed better on the x87 floating-point coprocessor that was widespread at the time, since it had to perform additional operations for the strict semantics (you can find more details in this Wikipedia article ).
Starting with Java 1.2, anyone who wanted to continue to use strict calculations had to strictfp
mark this with the keyword in the class definition:
1 |
|
Modern hardware can carry out the strict floating-point semantics without any loss of performance, so that in JDK Enhancement Proposal 306 it was decided to make this the standard semantics again from Java 17.
The strictfp
keyword is therefore superfluous. Using it results in a compiler warning:
1 |
|
New macOS Rendering Pipeline
In 2018, Apple marked the OpenGL library previously used by Java Swing for rendering under macOS as “deprecated” and presented the Metal framework as its successor.
JDK Enhancement Proposal 382 transitions the Swing rendering pipeline for macOS to the Metal API.
macOS/AArch64 Port
Apple has announced that it will switch Macs from x64 to AArch64 CPUs in the long term. Accordingly, a corresponding port is provided with JDK Enhancement Proposal 391 .
The code is an extension of the AArch64 ports for Linux and Windows released in Java 9 and Java 16 with macOS-specific adjustments.
New Page for “New API” and Improved “Deprecated” Page
In JavaDoc generated from Java 17, there is a “NEW” page that shows all new features grouped by version. To do this, the @since
tags of the modules, packages, classes, etc. are evaluated.
“NEW” page in JavaDoc generated since Java 17
In addition, the “DEPRECATED” page has been revised. Up until Java 16, here is an ungrouped list of all features marked as “deprecated”:
Java 16 DEPRECATED page
Starting with Java 17, we see the features marked as “deprecated” grouped by release:
Java 17 DEPRECATED page
There is no JDK Enhancement Proposal for this extension.
Complete list of all changes in Java 17
This article has presented all changes defined in JDK Enhancement Proposals (JEPs) as well as numerous class library extensions for which no JEPs exist. For more changes, especially to the security libraries, see the official Java 17 release notes .
Conclusion
Although Java 17 is the latest LTS release, this release is not significantly different from the previous ones. We got a mix of:
- new language features (sealed classes),
- API changes (
InstantSource
,HexFormat
, context-specific deserialization filters), - a performance improvement (asynchronous logging of the JVM),
- Deprecations and deletions (Applet API, Security Manager, RMI Activation, AOT and JIT Compiler)
- und neuen Preview- und Incubator-Features (Pattern Matching for switch, Foreign Function & Memory API, Vector API).
In addition, the path taken in Java 9 with Project Jigsaw has been brought to an end by removing the transitionally provided “Relaxed Strong Encapsulation” mode and access to private members of other modules (deep reflection) must always be explicitly released.
Did you like the article? Then please leave me a comment or share the article using one of the share buttons at the end.
Java 18 is just around the corner and the features it contains are certain. I will present these in the next article. Would you like to be informed when the article goes online? Then click here to sign up for the HappyCoders newsletter.