Java 17 has arrived, and we couldn’t be more excited as developers. This long-term support (LTS) release of the Java SE platform, available on September 15, 2021, brings many new features and improvements, making the language more powerful, efficient, and user-friendly. The release culminates in extensive collaboration between Oracle engineers and the worldwide Java developer community via the OpenJDK Community and the Java Community Process (JCP).
In this article, we’ll explore some of the most notable enhancements to the Java ecosystem introduced in Java 17. These include language features, runtime enhancements, and even experimental previews and incubators that pave the way for future developments. As we discuss these new features, we’ll also share code examples to demonstrate how they can be used in real-world applications.
We can leverage its features and performance improvements by staying up-to-date with the latest Java version to build more efficient, robust, and scalable applications. So, join us as we dive into the fresh and exciting world of Java 17 and learn how to harness its power for our projects.
- Java 17 Overview
- JDK Enhancement Proposals (JEPs) in Java 17
- Sealed Classes and Interfaces
- Pattern Matching and Switch Expressions
- Vector API and Performance Improvements
- Floating-Point Semantics
- Foreign Function and Memory API
- Enhanced Pseudo-Random Number Generators
- Context-Specific Deserialization Filters
- MacOS Rendering Pipeline and Metal API
- Deprecations and Feature Removals
- Java 17 vs 11 Performance
- Java and JDK Versions
Java 17 Overview
We are excited to discuss Java 17, the latest LTS release, with numerous improvements for better performance and stability.
This release follows a six-month cycle but receives long-term, extended support after that. It is ideal for businesses and developers requiring a maintained and stable application platform with multiple features.
New Features and Enhancements
Java 17 comes with several new features and enhancements that ease the everyday work of developers:
JEPs (Java Enhancement Proposals)
- JEP 356: Enhanced Pseudo-Random Number Generators (PRNG): This feature introduces new APIs for random number generation, allowing developers to have better control over the desired level of randomness.
- JEP 389: Foreign Function & Memory API (Incubator): This API simplifies working with native code by providing a pure Java API to call native libraries and work with native memory.
- JEP 411: Deprecate the Security Manager for removal: This proposal deprecates the Security Manager and its associated API for future removal. They are considered legacy components and are no longer suitable for modern applications.
- JEP 391: macOS/AArch64 Port: This feature brings the macOS/AArch64 port to JDK 17, enabling Java to run natively on Apple’s M1-powered devices.
Performance and Stability Improvements
Java 17 includes many optimizations and updates to improve the overall performance and stability of the platform. The team worked on garbage collection algorithms, Just-In-Time (JIT) compilation, and enhanced language features, resulting in faster application startup and reduced memory footprint.
JDK Enhancement Proposals (JEPs) in Java 17
New JEPs
In Java 17, several new JDK Enhancement Proposals (JEPs) have been introduced to improve the language, the JVM, and other aspects of the ecosystem. Some of the most notable JEPs include:
- JEP 356: Enhanced Pseudo-Random Number Generators
- JEP 389: Foreign Function & Memory API (Incubator)
- JEP 411: Deprecate the Security Manager for Removal
Let’s take a look at a few examples. With the future release of JEP 356, we now have access to several new options for generating random numbers:
import java.util.random.RandomGenerator; public class Example { public static void main(String[] args) { RandomGenerator rng = RandomGenerator.getDefault(); System.out.println(rng.nextInt()); } }
Updated JEPs
In addition to new JEPs introduced in Java 17, several existing JEPs have also received updates and improvements. Some examples of updated JEPs are:
- JEP 390: Warnings for Value-Based Classes
- JEP 394: Pattern Matching for Switch
- JEP 398: Deprecate the Applet API for Removal
For instance, JEP 394 brings an updated version of the pattern-matching syntax to the switch statement, which makes it more concise and expressive:
public enum DayOfWeek { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY; } public class Example { public static void main(String[] args) { DayOfWeek dayOfWeek = DayOfWeek.MONDAY; switch (dayOfWeek) { case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> System.out.println("Weekday"); case SATURDAY, SUNDAY -> System.out.println("Weekend"); } } }
Sealed Classes and Interfaces
In Java 17, one of the significant features introduced is sealed classes and interfaces. This enhancement, part of Project Amber, aims to provide more fine-grained control over inheritance in Java, allowing classes and interfaces to define their permitted subtypes.
Sealed classes have numerous benefits over other classes, including:
- Accurate representation of domain models
- Reduction of unnecessary code
- Enhanced readability and maintainability
To declare a sealed class, add the sealed modifier to the class declaration and specify the permitted subclasses using the permits clause:
public sealed class Shape permits Circle, Square, Rectangle { // class implementation }
We’ve defined Shape as a sealed class, allowing only Circles, Squares, and Rectangles to extend. These subclasses can now be defined with several options: sealed, non-sealed, or final. For example:
public final class Circle extends Shape { // class implementation }
By marking Circle as final, we ensure it cannot be extended further.
Alternatively, we could have used the sealed modifier to specify additionally permitted subclasses or the non-sealed modifier to allow unrestricted inheritance.
Sealed interfaces function similarly to sealed classes. The major difference is the use of an implements clause rather than extends. Here’s an example:
public sealed interface Vehicle permits Car, Truck { // interface methods } public final class Car implements Vehicle { // class implementation }
In summary, sealed classes and interfaces in Java 17 offer precise control over inheritance, resulting in cleaner, more expressive code. This feature can improve our code quality and accurately depict our domain models.
Pattern Matching and Switch Expressions
In Java 17, one of the key features introduced is pattern matching for switch expressions and statements. This enhancement is a part of Project Amber and aims to improve the expressiveness of the Java language while reducing boilerplate code. Let’s dive into pattern matching in switch expressions and how it can benefit us as developers.
Pattern matching for switch expressions extends the functionality of the traditional switch statement. Previously, the selector expression had to evaluate as a number, string, or enum constant, and case labels were limited to constants.
With pattern matching in Java 17, switch cases now support patterns, providing more flexibility when defining conditions for each case.
For example, let’s consider a simple shape hierarchy with a few classes:
abstract class Shape { /* ... */ } class Circle extends Shape { /* ... */ } class Rectangle extends Shape { /* ... */ }
Using pattern matching for switch expressions, we can now easily perform operations based on the type of shape, like this:
Shape shape = getShape(); // Retrieve a Shape object double area = switch (shape) { case Circle c -> Math.PI * c.radius() * c.radius(); case Rectangle r -> r.width() * r.height(); default -> throw new UnsupportedOperationException("Unrecognized shape"); };
Notice how the case labels in the switch expression now contain patterns, including type and destructuring patterns. Type patterns match a value with a specific type, and in our example, Circles and Rectangles act as type patterns.
On the other hand, destructuring patterns allow us to easily extract fields from an object, simplifying the code inside the switch block.
Another important aspect of pattern matching in switch expressions is the improvement in the readability of the code. With concise syntax and expressive patterns, developers can easily understand the intent and logic behind the code.
In summary, pattern matching for switch expressions and statements in Java 17 brings numerous benefits:
- Improved flexibility with patterns in case labels
- Enhanced readability and expressiveness of the code
- Simplified destructuring and working with object fields
Remember that this feature is a preview feature in Java 17, meaning it may be subject to changes or not present in future Java SE releases. However, it’s a significant step towards refining switch expressions and statements for better ease of use and increased code readability.
Vector API and Performance Improvements
In Java 17, we’ve seen significant advancements in the Vector API, designed to accelerate computations using Single Instruction Multiple Data (SIMD) style operations. This API allows us to leverage specialized CPU hardware supporting vector instructions, executing them as pipelines for improved performance.
The Vector API has undergone several iterations; it was first proposed by JEP 338 and integrated into Java 16. The second incubator, JEP 414, is part of Java 17, and a third incubator is in progress, currently targeted for Java 18 as JEP 417.
Java 17 introduces several notable changes and enhancements to the Vector API, including:
- Performance improvements
- API refinements in response to feedback
The Vector API aims to efficiently use multiple SIMD hardware platforms while working correctly on Java platforms without SIMD support. One exciting aspect of this API is its potential for improving the performance of machine learning, multimedia processing, and other compute-intensive tasks.
Regarding performance improvements, the Vector API benefits from the Just-In-Time (JIT) and Ahead-Of-Time (AOT) compiler optimizations. The JIT compiler optimizes during runtime, generating efficient machine code tailored to specific hardware. The AOT compiler, on the other hand, optimizes code during the compilation process, producing a faster startup time.
Here’s a simple Java code example demonstrating the use of the Vector API:
import jdk.incubator.vector.FloatVector; import jdk.incubator.vector.VectorSpecies; public class VectorExample { public static void main(String[] args) { VectorSpecies<Float> species = FloatVector.SPECIES_256; float[] a = new float[species.length()]; float[] b = new float[species.length()]; float[] c = new float[species.length()]; for (int i = 0; i < species.length(); i++) { a[i] = i; b[i] = i * 2; } FloatVector va = FloatVector.fromArray(species, a, 0); FloatVector vb = FloatVector.fromArray(species, b, 0); FloatVector vc = va.add(vb); vc.intoArray(c, 0); for (float element : c) { System.out.println(element); } } }
Floating-Point Semantics
In Java 17, one of the notable features is the restoration of always-strict floating-point semantics, as proposed by JEP 306. This change affects how floating-point operations are handled in the Java programming language and the Java Virtual Machine (JVM).
Before JEP 306, Java had two floating-point semantics: strict and default. The strictfp keyword was used to enforce strict floating-point semantics in a method or class. However, this led to inconsistencies as different parts of the code could use varying levels of floating-point precision.
Java 17 simplifies this by making default floating-point operations consistently strict across the entire codebase.
This change is particularly helpful for scientific applications that require high precision and consistent behaviour. As a result, Java developers can now rely on uniform floating-point semantics throughout their applications.
Consider the following example:
public class FloatingPointExample { public static void main(String[] args) { float a = 0.1f; float b = 0.2f; float sum = a + b; System.out.println("Sum: " + sum); } }
In prior versions of Java, depending on the use of strictfp, the precision of the sum variable could vary. However, with Java 17 and JEP 306’s always-strict floating-point semantics, the behaviour of this example becomes predictable and consistent.
Restoring always-strict floating-point semantics in Java 17 through JEP 306 has led to a more reliable and uniform way of handling floating-point operations. This benefits developers, particularly those working with scientific applications or other use cases that demand high precision and consistent behaviour.
Foreign Function and Memory API
With Java 17, we’ve been introduced to the Foreign Functions and Memory API (FFM API), part of Project Panama. This API aims to simplify interacting with native code and memory, improving efficiency and safety compared to the older Java Native Interface (JNI).
The FFM API provides various classes and interfaces for foreign memory and functions. Some of the key components include:
- MemorySegment: Represents a section of foreign memory.
- MemoryAddress: Represents an address within a MemorySegment.
- SegmentAllocator: Handles memory allocation in foreign segments.
We can also define and manipulate structured foreign memory using the MemoryLayout and VarHandle classes. The FFM API also allows managing foreign resources’ lifecycles using the ResourceScope class.
Here’s a simple example of using the FFM API to allocate foreign memory:
import jdk.incubator.foreign.*; try (ResourceScope scope = ResourceScope.newConfinedScope()) { MemorySegment segment = MemorySegment.allocateNative(100, scope); // Perform operations on the segment. }
The FFM API also makes it easier to call native libraries directly from Java.
For example, if we have a native library libmath.so that exports a function double cos(double), we can use the CLinker class to call this function:
import jdk.incubator.foreign.*; import jdk.incubator.foreignFunction.*; CLinker linker = CLinker.getInstance(); FunctionDescriptor funcDesc = FunctionDescriptor.of(CDouble.C_DOUBLE, CDouble.C_DOUBLE); LibraryLookup lookup = LibraryLookup.ofDefault(); SymbolLookup symbolResolver = lookup.lookup("cos", funcDesc); MethodHandle methodHandle = linker.downcallHandle(symbolResolver.get(), funcDesc); double angle = Math.toRadians(60); double result = (double) methodHandle.invokeExact(angle);
In this example, we used the CLinker, FunctionDescriptor, and LibraryLookup classes to get the native cos function’s address and create a MethodHandle to call it. The Foreign Function and Memory API thus help us achieve more efficient and safe interoperability between our Java programs and native code.
Enhanced Pseudo-Random Number Generators
Java 17 introduces an essential update to the API for random number generation with JEP 356. This update brings enhanced pseudo-random number generators (PRNGs) to the language, offering improved performance and more flexibility when working with random numbers.
The new API provides various interfaces and implementations for PRNGs that cater to different speed, space, period, and accidental correlation requirements. This makes it easier to use different algorithms interchangeably and supports stream-based programming.
One significant improvement is adding the SplittableRandom class as an alternative to the traditional java.util.Random class. Users can now split instances of this class into separate, independent generators, optimizing parallel computations and offering better performance in multi-threaded scenarios.
import java.util.random.SplittableRandom; public class SplittableRandomExample { public static void main(String[] args) { SplittableRandom random = new SplittableRandom(); System.out.println("Random (0 to 100): " + random.nextInt(101)); } }
Some of the key features of JEP 356 include:
- New interface types: RandomGenerator, RandomGeneratorFactory, and RandomGenerators
- Methods to list, find, and instantiate generator factories
- A set of new PRNG implementations, such as LCG64, Xoshiro256, and more
- Stream support for producing random numbers
Java 17’s enhanced PRNGs are crucial in applications like physical simulations, machine learning, and games requiring efficient and secure random number generation.
With JEP 356, Java developers can now access a more powerful and versatile random number generation API, making tackling complex problems with high-performance solutions a little easier.
Context-Specific Deserialization Filters
In Java 17, one of the significant enhancements to improve security and serialization is the introduction of context-specific deserialization filters with the implementation of JEP 415. It allows applications to configure context-specific deserialization filters dynamically on a per-operation basis via a JVM-wide filter factory. This promotes more secure and robust validation of serialized data, especially when dealing with untrusted sources.
To set up a context-specific deserialization filter, we must create a SerialFilterFactory that defines a BinaryOperator for selecting the appropriate filter. Here’s how we can use this new feature in our code:
import java.io.*; import java.util.function.*; public class Main { public static void main(String[] args) { ObjectInputFilter.Config.setSerialFilterFactory(filterFactory()); // other code... } private static BinaryOperator<ObjectInputFilter> filterFactory() { return (currentFilter, incomingFilter) -> { if (currentFilter == null) { return incomingFilter; } else { return currentFilter.merge(incomingFilter); } }; } }
In the example above, we first set the serial filter factory for the JVM by using ObjectInputFilter.Config.setSerialFilterFactory(). The createSerialFilterFactory() method returns a BinaryOperator that merges the current filter with the incoming filter and handles the case when the current filter is not set.
With JEP 415, it’s now easier to implement application-specific deserialization filtering policies without compromising the system’s overall security. This improvement in Java 17 significantly helps reduce potential security vulnerabilities and make our application more resilient.
Remember to always keep our filters up to date based on the deserialization contexts in our application, ensuring optimal security and preventing malicious attacks.
MacOS Rendering Pipeline and Metal API
In Java 17, the release of JEP 382 introduced a new macOS rendering pipeline using the Apple Metal API. This change was made because Apple deprecated the OpenGL API (in macOS 10.14), previously used internally in Swing GUI. Implementing the new rendering pipeline provides a fully functional pipeline for the Java 2D API that uses the macOS Metal framework.
Alongside the new macOS rendering pipeline, Java 17 also ports the JDK to the macOS/AArch64 platform via JEP 391. This supports the ARM-based Apple Silicon Macs, ensuring better performance and compatibility with these new devices.
The Apple Metal API is a low-overhead API with features designed to help developers create hardware-accelerated graphics on Apple platforms. It includes a rich shading language, tight integration between graphics and computing, and an extensive suite of GPU profiling and debugging development tools. With Java 17’s adoption of Metal, Java applications on macOS will now benefit from Metal’s capabilities.
While the new rendering pipeline does not require any changes to the existing Java APIs, developers can expect smoother graphics performance thanks to the improved rendering capabilities of the Apple Metal API. Note that this change primarily affects applications using Java 2D graphics on macOS and may not significantly impact those primarily focused on back-end workloads.
To illustrate the use of Java graphics with the new rendering pipeline, here’s an example of how we might create a simple window with a custom paint method using client-side Java code:
import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; public class Main { public static void main(String[] args) { JFrame frame = new JFrame("Metal Rendering Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(new Dimension(300, 300)); JPanel panel = new JPanel() { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.RED); g.fillRect(50, 50, 200, 200); } }; frame.add(panel); frame.setVisible(true); } }
In this example, we create a JFrame with a custom JPanel that overrides the paintComponent method, drawing a red rectangle on a 300×300 window. With the new macOS rendering pipeline, the application takes advantage of the Metal API, resulting in smoother graphics performance on macOS devices.
Deprecations and Feature Removals
In Java 17, several deprecations and feature removals have taken place, helping to streamline the language and remove legacy components. We will discuss some key deprecated features and the reasons behind their removal.
- Applet API: With advancements in web technology, the use of Applets has become obsolete. They have been deprecated since Java 9, and JDK 17 now entirely removes the Applet API. Modern alternatives like JavaScript and HTML5 have taken over in providing rich web content.
- Security Manager: The Security Manager was used to enforce security policies for Java applications, but more robust security mechanisms have replaced it. The deprecation of the Security Manager in Java 17 aligns with the move towards strong encapsulation and advanced security features.
- RMI Activation: Java 17 deprecates RMI Activation, a feature rarely used today. While RMI (Remote Method Invocation) is still part of Java, the activation system has been phased out. Alternatives like Jini and Java EE provide a better solution for distributed systems.
Some notable Java Enhancement Proposals (JEPs) directly impact deprecations and removals in Java 17:
- JEP 403: Strongly encapsulate JDK internals goal of the Java Platform Module System. JEP 403 aims to strengthen encapsulation by making it the default for all JDK internal modules. This change will prevent access to internal APIs and discourage fragile development practices.
- JEP 410: This proposal removes the Experimental AOT (Ahead-Of-Time) and JIT (Just-In-Time) compiler, Graal, which has been deprecated since Java 16. However, they will still be available in GraalVM releases.
Java 17 vs 11 Performance
As we dive into a brief discussion about performance between Java 11 and Java 17, it’s essential to understand the impact on performance.
Java 11 and 17 are Long-Term Support (LTS) versions; the incremental updates have resulted in numerous performance improvements. Some of these updates are related to syntax, APIs, and community contributions.
While the most notable performance enhancement is seen in specific use cases and target platforms, the overall performance of the JDK has improved between the JDK 11 and JDK 17 LTS releases.
Java and JDK Versions
In our journey as Java developers, we have encountered several versions of Java and JDK. Let’s look at some significant versions and how they enhanced the Java ecosystem.
Java 7 (JDK 7): Released in July 2011, Java 7 introduced the following features:
- Project Coin: Small language improvements such as using Strings in switch statements and try-with-resources blocks.
- NIO.2: Improved file system access and asynchronous I/O capabilities.
- Fork/Join framework: For parallel programming support.
Java 9 (JDK 9): Launched in September 2017, Java 9 came with the following significant features:
- Java Platform Module System (JPMS): Java’s module system breaks down the Java runtime into smaller, more manageable modules.
- JShell: An interactive Java REPL (Read-Eval-Print Loop) tool for quickly testing Java code snippets.
Java 10 (JDK 10): Released in March 2018, Java 10 brought us these enhancements:
- Local-variable type inference: The var keyword allows the Java compiler to infer the type of a local variable.
- Consolidation of the JDK forest into a single repository.
Java 11 (JDK 11): Launched in September 2018, Java 11 is an LTS (Long-Term Support) release. Its notable features include:
- New HTTP client: A new HTTP library replaces the outdated HttpURLConnection class.
- Link tool: For creating custom Java runtime images with selected modules.
- Epsilon garbage collector: A no-op GC for performance testing and short-lived applications.
Java 12 (JDK 12): Released in March 2019, Java 12 came with these key changes:
- Switch expressions (Preview): Simplifying the syntax for switch statements.
- JVM Constants API: Allowing low-level interaction with the Java class file format.
Java 14 and 15, as intermediary releases, also introduced experimental new features in Java like pattern matching, records, and sealed classes.
Java 17 (JDK 17): Launched in September 2021, Java 17 is the LTS release. Its main features include:
- Sealed classes: Offering a way to create restricted hierarchies to improve expressiveness and safety.
- Pattern matching for a switch (Preview): Extending pattern matching capabilities to switch expressions and statements.
GraalVM: GraalVM is a high-performance runtime that supports multiple languages, including Java. It provides a Just-In-Time (JIT) compiler and an Ahead-Of-Time (AOT) compiler for creating native executables.
To sum up, throughout the evolution of Java and JDK versions, we have witnessed significant improvements like modules, language enhancements, and performance gains. Java continues to adapt and prove itself as a versatile and powerful programming language with every new version.