How to use the new features of Java 17
Java 17 was released almost 2 years ago in September 2021, but this version of the popular programming language might be worth looking into if you’re starting a new project or looking to upgrade your version of Java to take advantage of improved features.
Java 17 is a long-term support version, which means that it will receive updates, including security updates, for a long time. Simply put, this is the version that will be used in the industry for at least a few years, just as the previous long-term support version of Java 11 is currently used.
If you want to use the latest versions of the Spring 6 and Spring Boot 3 frameworks, you will also need Java version 17 or higher.
What has been improved in Java 17
Java 17 contains many improvements, but we will focus on those that can improve application performance and security. If you need to change your code to take advantage of new features and language features, you can simply upgrade the JDK version to get performance gains.
Performance
Java 17 is faster than Java 11 by 6-16% depending on the garbage collector used:
- 8.66% faster for the default garbage collector (G1GC – default garbage collector)
- 6.54% faster for Parallel Garbage Collector (ParallelGC – Parallel Garbage Collector)
Java 17 is also somewhat faster than Java 16.
Safety
In terms of security, changes have been made in several areas:
1. Parts of the code were removed that were already marked as deprecated, or were experimental and not widely used:
- applet API
- RMI activation
- security manager
- Experimental AOT and JIT compilers
2. The command line switch was turned off, which allowed weakening strict encapsulation
3. New programming language features have been added:
- Sealed (closed) classes – sealed classes
- Context-Specific Deserialization Filters
Let’s dwell on the new features of the Java 17 language in more detail.
Sealed classes and interfaces
A sealed class allows you to restrict or select its subclasses. A class cannot extend a sealed class if it is not in the list of allowed child classes of the parent class. The main motive here is to allow the superclass to be widely available but not widely extensible.
The class is sealed using the sealed keyword. The sealed class must be followed by the permits keyword, along with a list of classes that can extend it. Sealed interfaces can be created in the same way. Here is a simple example of a sealed class:
public abstract sealed class Device permits Laptop, Smartphone {...}
The class that extends it must have one of the modifiers:
- final – the class cannot be further extended
- sealed – we need to specify classes that can extend it using the permits modifier
- non-sealed – the class becomes available to the standard extension again
Example:
public non-sealed class Laptop extends Device {...}
Context-Specific Deserialization Filters
Serialization is a mechanism for converting the state of an object in memory into a stream of bytes and saving the object to a file. A simple serialization example:
Laptop laptop = new Laptop();
String filename = "laptop.ser";
FileOutputStream file = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(file);
out.writeObject(laptop);
out.close();
file.close();
Deserialization is the reverse process where a stream of bytes is used to recreate an actual Java object in memory. Example:
String filename = "laptop.ser";
FileInputStream file = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(file);
Laptop laptop = (Laptop)in.readObject();
in.close();
file.close();
A Java object is serializable if its class or any of its superclasses implements either the java.io.Serializable interface or a java.io.Externalizable subinterface.
Deserializing untrusted data can lead to vulnerabilities that could allow an attacker to execute arbitrary code.
Laptop laptop = (Laptop)in.readObject();
The readObject method on java.io.ObjectInputStream will create a serializable object of the Laptop class. If the created object has any sensitive data, such as remote code execution, then this can cause a security issue in the system.
To solve this problem, deserialization filters were introduced back in Java 9. They allow you to check the data stream before it is deserialized. This can be done statically or dynamically via the ObjectInputFilter API.
FileInputStream file = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(file);
ObjectInputFilter filesOnlyFilter = ObjectInputFilter.Config.createFilter("!com.example.Laptop;");
in.setObjectInputFilter(filesOnlyFilter);
Laptop laptop = (Laptop)in.readObject();
Java 17 adds more flexibility to deserialization filters, now it’s possible to use FilterFactory. The following code uses a merge of the existing and new filters:
ObjectInputFilter.Config.setSerialFilterFactory((filter1, filter2) -> ObjectInputFilter.merge(filter2,filter1));
It’s also possible to create filters in human-readable code via the allowFilter() and rejectFilter() methods on ObjectInputFilter.
Outcome
If you’re planning to upgrade your version of Java to improve performance and security, and to take advantage of new language features, Java 17 looks like a good candidate, as it’s a long-term support version that’s faster and more secure than the previous ones.
about the author
Sergey Starikov (Siarhei Starykau), Solution Architect at EPAM Systems, has been working with the Java programming language for 18 years.