You are here
Home > java > Core Java >

Java 17 Features

Java 17 FeaturesUndoubtedly, the demand of Java 17 features became crucial after the release of Spring Boot 3.0. Needless to say, the use of Java 17 has become mandatory since Spring Boot 3.0. It also means that every Java developer should be aware of the new changes introduced in Java 17. Java 17 comes with several new features and enhancements that aim to improve developer productivity and program efficiency. In this article, we will explore some of the most significant Java 17 features, with code examples to help you understand how to use them effectively.

Java 17 is an LTS (Long-term support) release of the Java programming language. As it is an LTS version, it will receive prolonged support and security updates for a minimum of eight years. Java 17 comes with various new features that is inclined to improve developer productivity and program efficiency. In this article, we will explore some of the most essential developer’s friendly Java 17 features and their code examples.

You may also go through: ‘How to add JDK 17 support in Eclipse?

Java 17 Features

In Java 17, apart from new features, some of the features got finalized which was introduced as a preview feature in the lower versions. They will also be part of Java 17. Let’s discuss about all of them one by one in the following sections.

Pattern Matching For Switch Statements

Java 17 introduced a new feature called “Pattern Matching for switch Statements” that allows developers to simplify the code for switch statements that involve pattern matching. This feature provides a short and more readable syntax to handle multiple conditions in switch statements.

Before the introduction of this feature, switch statements could only compare the value of a single variable against a series of constants or expressions. Moreover, the previous version of switch statement was limited to byte, short, char, int, Byte, Character, Short, Integer, String, and Enum types. The new feature allows developers to use patterns to match against the value of an object of any type.

Before getting into enhanced switch statements in Java 17 features, let’s get into some background of it.

Note: case statements can now be written as `case …expression ->’ instead of  ‘case expression:’ since Java 12.

Traditional Switch Statement

Here is an example of a traditional switch statement without pattern matching:

public static String getDayOfWeek(int dayNum) {
    String day;
    switch (dayNum) {
        case 1:
            day = "Monday";
            break;
        case 2:
            day = "Tuesday";
            break;
        case 3:
            day = "Wednesday";
            break;
        case 4:
            day = "Thursday";
            break;
        case 5:
            day = "Friday";
            break;
        case 6:
            day = "Saturday";
            break;
        case 7:
            day = "Sunday";
            break;
        default:
            day = "Invalid day";
    }
    return day;
}

Switch Statements Since Java 12

Since the new Java 12 features, the same code can be simplified as follows:

public static String getDayOfWeek(int dayNum) {
    return switch (dayNum) {
        case 1 -> "Monday";
        case 2 -> "Tuesday";
        case 3 -> "Wednesday";
        case 4 -> "Thursday";
        case 5 -> "Friday";
        case 6 -> "Saturday";
        case 7 -> "Sunday";
        default -> "Invalid day";
    };
}

As shown in the example, since Java 12, the case statements now use the new arrow operator (->) to specify the pattern and the result expression. It also eliminates the break statement at each case.

Switch Statements Since Java 17

In addition to using constant patterns, the new feature of java 17 also allows developers to use variable patterns and type patterns. A variable pattern allows you to match against a specific value and assign it to a variable. A type pattern allows you to match against the type of a value.

Here is an example that demonstrates the use of variable and type patterns:

public static int getLength(Object obj) {
    return switch (obj) {
        case String s -> s.length(); // variable pattern
        case List list && !list.isEmpty() -> list.size(); // type pattern
        case null -> 0;
        default -> -1;
    };
}

In the example, the switch expression matches against the object passed as an argument. The first case statement uses a variable pattern to match against a string and assign it to a variable ‘s’, which is then used to return the length of the string. The second case statement uses a type pattern to match against a non-empty list and returns its size. The last case statement matches against null and returns 0. Note that we can also take a null case now. The default case statement returns -1.

Furthermore, this allows you to use expressions as case labels instead of just constants.
Here is an example of how to use the enhanced switch statement with expression labels:
int value = 5;
switch (value) {
    case 1 -> System.out.println("One");
    case 2 -> System.out.println("Two");
    case 3, 4 -> System.out.println("Three or Four");
    case n if n >= 5 && n <= 10 -> System.out.println("Between Five and Ten"); // expression pattern 
    default -> System.out.println("Invalid value");
}

In this example, the switch statement checks the value of the value variable. The case statements are written using the enhanced syntax, which allows for expression labels and additional conditions using the if keyword.

Below example shows the summary of types that are allowed in a case label:

public String testType(Object obj) {  
    return switch (obj) { 
        case null -> "NULL value"; 
        case String str -> "It's a String";  
        case Size s -> "Enum Type"; 
        case Point p -> "Records Type"; 
        case Employee e -> "Custom Object's Type"; 
        case int[] ai -> "Array Type"; 
        case Employee e && e.getName.equals("John") -> "Conditional Statement";  
        default -> "Unknown"; 
     }; 
} 

public enum Size {  
   SMALL, MEDIUM, LARGE; 
} 

public record Point( int i, int j) { ..... } 

public class Employee{ ..... }

In the example above, record is a new type introduced in Java 14. To know more about records type, kindly go through record type in Java 14 under Java 14 features.

Pattern Matching for instanceof

Java 17 introduces a short way of pattern matching for the instanceof operator, which helps to simplify the code required for type checks. With pattern matching, you can extract the value of the checked object into a new variable if the check succeeds. This new variable can then be used in following code. It eliminates the need to cast the object into the expected type.

Note that this was a preview feature introduced under Java 14 Features. It has become a permanent feature under the list of Java 17 features.

Here is an example of how pattern matching can simplify code that uses the instanceof operator:

// Old way with pattern matching (Before Java 17)

if (obj instanceof String) {
  String str = (String) obj;
  System.out.println(str.length());
}
// New way with pattern matching (Since Java 17)

if (obj instanceof String str) {
System.out.println(str.length());
}

In the old way, you need to cast the object to the String type before using its methods. In the new way, you can use pattern matching to extract the value of obj as a String and assign it to the str variable.

Sealed Classes & Interfaces

Sealed classes and interfaces offer a restriction whether classes/interfaces can extend/implement other classes/interfaces or not.

In simple words, Sealed Classes work by specifying which classes or interfaces are allowed to extend or implement a particular class or interface. This is achieved by using the “sealed” modifier on the parent class or interface, and then specifying the allowed subclasses or implementing classes using the “permits” keyword.

Sealed Classes were proposed by JEP 360 and delivered in JDK 15 as a preview feature. They were proposed again, with some refinements, and delivered in JDK 16 as a preview feature. Now in JDK 17, Sealed Classes are being finalized with no changes from JDK 16.

Let’s understand it with an example of a sealed class:

public sealed class Shape permits Circle, Square, Triangle {
    // ...
}

In the example, the “sealed” modifier is used to specify that the Shape class is a sealed class. The “permits” keyword is then used to specify the allowed subclasses of Shape, which are Circle, Square, and Triangle.

By using a sealed class, we can ensure that the only allowed subclasses of Shape are Circle, Square, and Triangle. Any other attempt to extend Shape will result in a compilation error.

In addition to specifying allowed subclasses, sealed classes also allow developers to define “non-sealed” or “final” subclasses that cannot be further extended. Here is an example of a non-sealed subclass:

public non-sealed class Circle extends Shape {
    // ...
}

In the example, the “non-sealed” modifier is used to specify that the Circle class is a non-sealed subclass of Shape. This means that Circle can be extended by any other class, but it cannot be further extended itself.

Sealed classes also allow developers to define “permitted” interfaces that can be implemented by the allowed subclasses. Here is an example:

public sealed class Shape permits Circle, Square, Triangle
        implements Drawable, Resizable {
    // ...
}

In the example, the Shape class implements the Drawable and Resizable interfaces, which are permitted to be implemented by its allowed subclasses.

Sealed classes provide several benefits, including increased type safety, improved code readability, and reduced coupling.

For further details & examples on sealed classes, kindly visit sealed classes in Java 15.

Enhanced Pseudo-Random Number Generators

“Enhanced Pseudo-Random Number Generators” introduced under Java 17 Features. This feature provides additional pseudo-random number generators (PRNGs) that can be used to generate random numbers in Java applications. The new PRNGs are designed to be faster and more secure than the existing PRNGs in Java.

The new PRNGs are implemented as instances of the SplittableRandom class, which provides a way to generate a sequence of pseudo-random numbers that can be split and used to create new PRNG instances. This allows multiple threads to generate random numbers independently and with low contention, which can improve performance in multi-threaded applications.

For example, below code demonstrates the concept of using the SplittableRandom class to generate random numbers:

SplittableRandom random = new SplittableRandom();
int randomNumber = random.nextInt();

In the example, we create a new instance of the SplittableRandom class and use its nextInt() method to generate a random integer. The nextInt() method returns a pseudo-random integer that is uniformly distributed between Integer.MIN_VALUE and Integer.MAX_VALUE.

We can also use the SplittableRandom class to generate random numbers in a range, For example:

SplittableRandom random = new SplittableRandom();
int randomNumberInRange = random.nextInt(10, 20);

In the example, we use the nextInt(int bound) method to generate a pseudo-random integer that is uniformly distributed between 10 (inclusive) and 20 (exclusive).

Another useful feature of the SplittableRandom class is that it allows us to split the PRNG and create new instances that generate independent sequences of random numbers. For example, below code demonstrates the concept:

SplittableRandom random1 = new SplittableRandom();
SplittableRandom random2 = random1.split();

In the example, we create a new instance of the SplittableRandom class and then split it to create a new instance that generates an independent sequence of random numbers. The two instances can be used in separate threads to generate random numbers with low contention.

Restore Always-Strict Floating-Point Semantics

In Java 17, a new feature called “Restore Always-Strict Floating-Point Semantics” has been introduced. This feature aims to improve the consistency and reliability of floating-point arithmetic in Java applications. It can be mainly important in applications where precision is critical.

Prior to Java 17, some floating-point operations in Java were not always strictly adhering to the IEEE 754 floating-point standard, which can lead to inconsistent behavior across different platforms and architectures. With the new feature, Java will always use the strictest possible floating-point semantics by default, which should result in more predictable and consistent behavior across different platforms.

For example, let’s observe how this feature works:

double a = 0.1;
double b = 0.2;
double c = a + b;
System.out.println(c);

In this example, we are adding two doubles a and b and storing the result in c. Prior to Java 17, the result of this operation would have been 0.30000000000000004 due to rounding errors that can occur in floating-point arithmetic. However, with the new feature in Java 17, the result will be 0.3 as per the IEEE 754 floating-point standard.

This can result in more consistent behavior across different platforms and architectures, which can be especially important in scientific and financial applications where precision is critical.

It’s worth noting that this feature may result in a slight performance hit for some floating-point operations, but this is generally considered a small price to pay for the increased consistency and reliability of floating-point arithmetic.

Strongly Encapsulate JDK Internals

The “Strongly Encapsulate JDK Internals” feature in Java 17 aims to further enhance the encapsulation of internal APIs in the JDK (Java Development Kit). The goal is to limit the use of internal APIs by third-party applications and libraries, which can improve the security and stability of Java applications.

Internal APIs are not intended for use by third-party applications, as they may change or be removed without notice in future JDK releases. However, some third-party libraries and applications still rely on these internal APIs, which can result in compatibility issues and security vulnerabilities.

With the “Strongly Encapsulate JDK Internals” feature in Java 17, access to internal APIs is more restricted. Any code that tries to access internal APIs will result in a warning message or a compilation error, depending on the specific API.

Let’s understand with an example how this feature works:

import sun.misc.Unsafe;

public class MyClass {
  public static void main(String[] args) {
    Unsafe unsafe = Unsafe.getUnsafe();
    long value = unsafe.allocateMemory(1024);
    System.out.println(value);
  }
}

In this example, we are trying to use the sun.misc.Unsafe class, which is an internal API that is not intended for use by third-party applications. Prior to Java 17, this code would compile and run without any issues. However, with the new feature, this code will result in a compilation error, as access to the sun.misc.Unsafe class is now strongly encapsulated.

To fix this issue, we can use an alternative API that is intended for use by third-party applications. For example, we can use the java.nio.ByteBuffer class to allocate memory:

import java.nio.ByteBuffer;

public class MyClass {
  public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    long value = ((sun.nio.ch.DirectBuffer) buffer).address();
    System.out.println(value);
  }
}

In this updated example, we use the ByteBuffer class to allocate memory instead of the sun.misc.Unsafe class. We then cast the ByteBuffer to a sun.nio.ch.DirectBuffer and use its address() method to get the memory address.

New macOS Rendering Pipeline

The “New macOS Rendering Pipeline” feature in Java 17 improves the performance and reliability of Java graphics on macOS by introducing a new rendering pipeline. This pipeline uses the Apple Metal API, which provides low-level access to the graphics hardware on macOS, resulting in faster and smoother graphics rendering.

Prior to Java 17, Java applications on macOS used the OpenGL pipeline, which can be slower and less reliable than the new Metal pipeline. The Metal pipeline provides better performance and reliability by allowing Java applications to directly access the graphics hardware on macOS, resulting in smoother and more responsive graphics rendering.

Let’s understand it with an example how this feature works:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MyPanel extends JPanel {
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    g2d.setColor(Color.RED);
    g2d.fillRect(10, 10, 50, 50);
  }
  
  public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.add(new MyPanel());
    frame.setSize(200, 200);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
  }
}

In this example, we created a simple JPanel that draws a red rectangle using the Graphics2D class. Prior to Java 17, this code would use the OpenGL rendering pipeline to draw the rectangle. However, with the new feature, this code will use the Metal rendering pipeline on macOS, resulting in faster and smoother graphics rendering.

The new Metal pipeline should be particularly noticeable in applications that use complex graphics or animations, as it can provide significant performance improvements over the old OpenGL pipeline.

Deprecate the Applet API for Removal

The “Deprecate the Applet API for Removal” feature of Java 17 involves marking the Applet API as deprecated, which means that it is no more recommended for use and may be removed in a future version of Java. The Applet API is used to create Java applets, which are small applications that run within a web browser.

Below is an example of how the Applet API can be used in Java:

import java.applet.Applet;
import java.awt.Graphics;

public class MyApplet extends Applet {
   public void paint(Graphics g) {
      g.drawString("Hello, world!", 50, 25);
   }
}

In this example, we defined a class called MyApplet that extends the Applet class. The paint method is overridden to draw the message “Hello, world!” on the applet’s canvas.

With the deprecation of the Applet API, it is recommended to use alternative technologies such as Java Web Start, JavaFX, or HTML5 for developing web-based applications. Developers should update their code to remove the usage of the Applet API and adopt the recommended alternatives to ensure the compatibility of their applications with future versions of Java.

FAQs

Is Java 17 an LTS release?

Yes, Java 17 is an LTS (Long-Term Support) release, that means it will receive updates and bug fixes for an extended period, making it a stable option for long-term projects.

How can we download and install Java 17?

We need to download the zip file by clicking the link on Java 17 download page according to our operating system. Alternatively, we can use package managers like SDKMAN or adopt OpenJDK distributions, which provide Java 17 builds.

Are there any IDEs that support Java 17 out of the box?

Yes, popular Integrated Development Environments (IDEs) like IntelliJ IDEA, Eclipse, and NetBeans typically provide support for the latest Java versions, including Java 17. We can configure our IDE to use Java 17 for development. We have a separate article on ‘How to add JDK 17 support in Eclipse?’.

What is the recommended approach for migrating Java applications to Java 17?

To migrate Java applications to Java 17, it is recommended to follow a systemic approach:

  • Review the release notes and documentation of Oracle website for compatibility and migration guidelines.
  • Test your application with Java 17 to identify and address any issues.
  • Update your codebase to use new features and APIs as required.
  • Ensure that your build and deployment processes are compatible with Java 17.

Why is Java 17 becoming so essential for a Java Developer?

As a Java developer, if you are working in a Spring Boot based application and using Spring Boot 3.0 and higher version, you must have to use Java 17. It’s a mandatory prerequisite to work in Spring Boot 3.0+ versions.

Conclusion

Apart from these, Java 17 also includes several other smaller improvements, but it’s a bit difficult to include all the features here in a single post. However, we have tried to cover all those features which are important to know as a developer, and to stay up-to-date with the latest releases of Java.
You may also go through the detailed article on Record In Java with examples, which was finalized in Java 17.

References 

Leave a Reply


Top