Main Tutorials

What is new in Java 17

Java 17 logo

Java 17 is a long-term support (LTS) release, reached General Availability on 14 September 2021, download Java 17 here.

Java 17 has 14 JEP items.

Java 17 developer features.
pseudorandom number generator, pattern matching for switch (preview), sealed class (standard feature), foreign function & memory API (incubator), dynamic deserialization filters.

1. JEP 306: Restore Always-Strict Floating-Point Semantics

This JEP is for numerically-sensitive programs, mainly scientific purposes; It again made default floating-point operations strict or Strictfp, ensure the same results from the floating-point calculations on every platform.

Short history

  1. Before Java 1.2, all floating-point calculations were strict; and it caused overheats on x87-based hardware.
  2. Since Java 1.2, we need the keyword strictfp to enable the strict floating-point calculation. The default floating-point calculation was changed from strict to subtly different floating-point calculations (avoid overheats issues).
  3. Now, since Intel and AMD have both support SSE2 (Streaming SIMD Extensions 2) extensions, which could support for the strict JVM floating-point operations without the overheats, so, the previous (before Java 1.2) overheats issues on x87-based hardware are irreverent in nowaday hardware.
  4. Java 17 restores the pre-Java 1.2 strict floating-point calculations as default, which means the keyword strictfp is now optional.

2. JEP 356: Enhanced Pseudo-Random Number Generators

This JEP introduced a new interface called RandomGenerator to make future pseudorandom number generator (PRNG) algorithms easier to implement or use.

RandomGenerator.java

package java.util.random;

public interface RandomGenerator {
  //...
}

The below example uses the new Java 17 RandomGeneratorFactory to get the famous Xoshiro256PlusPlus PRNG algorithms to generate random integers within a specific range, 0 – 10.

JEP356.java

package com.mkyong.java17.jep356;

import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;

public class JEP356 {

  public static void main(String[] args) {

      // legacy
      // RandomGeneratorFactory.of("Random").create(42);

      // default L32X64MixRandom
      // RandomGenerator randomGenerator = RandomGeneratorFactory.getDefault().create();

      // Passing the same seed to random, and then calling it will give you the same set of numbers
      // for example, seed = 999
      RandomGenerator randomGenerator = RandomGeneratorFactory.of("Xoshiro256PlusPlus").create(999);

      System.out.println(randomGenerator.getClass());

      int counter = 0;
      while(counter<=10){
          // 0-10
          int result = randomGenerator.nextInt(11);
          System.out.println(result);
          counter++;
      }

  }
}

Output

Terminal

class jdk.random.Xoshiro256PlusPlus
4
6
9
5
7
6
5
0
6
10
4

The below code generates all the Java 17 PRNG algorithms.


  RandomGeneratorFactory.all()
              .map(fac -> fac.group()+ " : " +fac.name())
              .sorted()
              .forEach(System.out::println);

Output

Terminal

LXM : L128X1024MixRandom
LXM : L128X128MixRandom
LXM : L128X256MixRandom
LXM : L32X64MixRandom
LXM : L64X1024MixRandom
LXM : L64X128MixRandom
LXM : L64X128StarStarRandom
LXM : L64X256MixRandom
Legacy : Random
Legacy : SecureRandom
Legacy : SplittableRandom
Xoroshiro : Xoroshiro128PlusPlus
Xoshiro : Xoshiro256PlusPlus

Java 17 also refactored the legacy random classes like java.util.Random, SplittableRandom and SecureRandom to extend the new RandomGenerator interface.

3. JEP 382: New macOS Rendering Pipeline

Apple deprecated the OpenGL rendering library in macOS 10.14 release(September 2018), in favor of the new Metal framework for better performance.

This JEP changes the Java 2D (like Swing GUI) internal rendering pipeline for macOS from Apple OpenGL API to Apple Metal API; this is an internal change; there are no new Java 2D APIs, nor change any existing APIs.

4. JEP 391: macOS/AArch64 Port

Apple has a long-term plan to transition its Mac from x64 to AArch64 (e.g., Apple M1 processors).

This JEP port JDK to run on AArch64 platforms on macOS.

Further Reading

5. JEP 398: Deprecate the Applet API for Removal

The Java Applet API is irrelevant because most of the web-browser has removed support for Java browser plug-ins.

Java 9 deprecated the Applet API.


@Deprecated(since = "9")
public class Applet extends Panel {
  //...
}

This JEP marked the Applet API for removal.


@Deprecated(since = "9", forRemoval = true)
@SuppressWarnings("removal")
public class Applet extends Panel {
  //...
}

6. JEP 403: Strongly Encapsulate JDK Internals

Many third-party libraries, frameworks, and tools are accessing the internal APIs and packages of the JDK. The Java 16, JEP 396 make the strong encapsulation by default (we are not allowed to access the internal APIs easily). However, we can still use --illegal-access to switch to the simple encapsulation to still access the internal APIs.

This JEP is the successor to the above Java 16 JEP 396, and it takes one more step by removing the --illegal-access option, which means we have no ways to access the internal APIs, except for critical internal APIs such as sun.misc.Unsafe.

Try the --illegal-access=warn in Java 17.

Terminal

java --illegal-access=warn

OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0  

7. JEP 406: Pattern Matching for switch (Preview)

This JEP adds pattern matching for switch statements and expressions. Since this is a preview feature, we need to use --enable-preview option to enable it.

7.1 if…else chain

Before Java 17, typically, we use a chain of if...else tests for several possibilities.

JEP406.java

package com.mkyong.java17.jep406;

public class JEP406 {

  public static void main(String[] args) {

      System.out.println(formatter("Java 17"));
      System.out.println(formatter(17));

  }

  static String formatter(Object o) {
      String formatted = "unknown";
      if (o instanceof Integer i) {
          formatted = String.format("int %d", i);
      } else if (o instanceof Long l) {
          formatted = String.format("long %d", l);
      } else if (o instanceof Double d) {
          formatted = String.format("double %f", d);
      } else if (o instanceof String s) {
          formatted = String.format("String %s", s);
      }
      return formatted;
  }

}

In Java 17, we can rewrite the above code like this:

JEP406.java

package com.mkyong.java17.jep406;

public class JEP406 {

    public static void main(String[] args) {

        System.out.println(formatterJava17("Java 17"));
        System.out.println(formatterJava17(17));

    }

    static String formatterJava17(Object o) {
        return switch (o) {
            case Integer i -> String.format("int %d", i);
            case Long l    -> String.format("long %d", l);
            case Double d  -> String.format("double %f", d);
            case String s  -> String.format("String %s", s);
            default        -> o.toString();
        };
    }

}

7.2 Pattern matching and null

Now we can test the null in switch directly.

Old code.

JEP406.java

package com.mkyong.java17.jep406;

public class JEP406 {

  public static void main(String[] args) {

      testString("Java 16");  // Ok
      testString("Java 11");  // LTS
      testString("");         // Ok
      testString(null);       // Unknown!
  }

  static void testString(String s) {
      if (s == null) {
          System.out.println("Unknown!");
          return;
      }
      switch (s) {
          case "Java 11", "Java 17"   -> System.out.println("LTS");
          default                     -> System.out.println("Ok");
      }
  }

}

New code.

JEP406.java

package com.mkyong.java17.jep406;

public class JEP406 {

    public static void main(String[] args) {

        testStringJava17("Java 16");  // Ok
        testStringJava17("Java 11");  // LTS
        testStringJava17("");         // Ok
        testStringJava17(null);       // Unknown!
    }

    static void testStringJava17(String s) {
        switch (s) {
            case null                   -> System.out.println("Unknown!");
            case "Java 11", "Java 17"   -> System.out.println("LTS");
            default                     -> System.out.println("Ok");
        }
    }

}

7.3 Refining patterns in switch

Review the below code snippet. To test the Triangle t and t.calculateArea(), we need to create an extra if condition.


class Shape {}
  class Rectangle extends Shape {}
  class Triangle  extends Shape {
      int calculateArea(){
          //...
      } }

  static void testTriangle(Shape s) {
      switch (s) {
          case null:
              break;
          case Triangle t:
              if (t.calculateArea() > 100) {
                  System.out.println("Large triangle");
                  break;
              }else{
                  System.out.println("Triangle");
              }
          default:
              System.out.println("Unknown!");
      }
  }

Java 17 allows so-called redefined patterns or guarded patterns like below:


  static void testTriangle2(Shape s) {
      switch (s) {
          case null ->
                  {}
          case Triangle t && (t.calculateArea() > 100) ->
                  System.out.println("Large triangle");
          case Triangle t ->
                  System.out.println("Triangle");
          default ->
                  System.out.println("Unknown!");
      }
  }

Further Reading

For more examples and explanation please visit this JEP 406: Pattern Matching for switch (Preview)

8. JEP 407: Remove RMI Activation

Java 15, JEP385 deprecated the RMI Activation for removal.

This JEP removed the RMI Activation or java.rmi.activation package.

9. JEP 409: Sealed Classes

Java 15, JEP 360 and Java 16, JEP 397 introduced [sealed class(https://cr.openjdk.java.net/~briangoetz/amber/datum.html) as a preview feature.

This JEP finalized the sealed classes as the standard feature in Java 17, with no changes from Java 16.

The sealed classes and interfaces control or restrict who can be a subtype.


public sealed interface Command
    permits LoginCommand, LogoutCommand, PluginCommand{
    //...
}

Further Reading

10. JEP 410: Remove the Experimental AOT and JIT Compiler

Java 9, JEP 295 introduced the Ahead-of-time compilation (the jaotc tool) as an experimental feature. Later Java 10, JEP 317 proposed it again as an experimental JIT compiler.

However, this feature has little use since they were introduced and required significant effort to maintain it, so this JEP removed the experimental Java-based ahead-of-time (AOT) and just-in-time (JIT) compiler

The following AOT packages, classes, tools and codes are removed:

  • jdk.aot — the jaotc tool
  • jdk.internal.vm.compiler — the Graal compiler
  • jdk.internal.vm.compiler.management — Graal’s MBean
  • src/hotspot/share/aot — dumps and loads AOT code
  • Additional code guarded by #if INCLUDE_AOT

11. JEP 411: Deprecate the Security Manager for Removal

Java 1.0 introduced the Security Manager to secure the client-side Java code, and irrelevant now.

This JEP deprecates the Security Manager for removal.

SecurityManager.java

package java.lang;

 * @since   1.0
 * @deprecated The Security Manager is deprecated and subject to removal in a
 *       future release. There is no replacement for the Security Manager.
 *       See <a href="https://openjdk.java.net/jeps/411">JEP 411</a> for
 *       discussion and alternatives.
 */
@Deprecated(since="17", forRemoval=true)
public class SecurityManager {
  //...
}

12. JEP 412: Foreign Function & Memory API (Incubator)

This Foreign Function & Memory API allows the developer to access the code outside the JVM (foreign functions), data stored outside the JVM (off-heap data), and accessing memory not managed by the JVM (foreign memory).

P.S This is an incubating feature; need add --add-modules jdk.incubator.foreign to compile and run the Java code.

History

  • Java 14 JEP 370 introduced Foreign-Memory Access API (Incubator).
  • Java 15 JEP 383 introduced Foreign-Memory Access API (Second Incubator).
  • Java 16 JEP 389 introduced Foreign Linker API (Incubator).
  • Java 16 JEP 393 introduced Foreign-Memory Access API (Third Incubator).
  • Java 17 JEP 412 introduced Foreign Function & Memory API (Incubator).

Please refer to previous Foreign Linker API examples in Java 16.

13. JEP 414: Vector API (Second incubator)

Java 16, JEP 414 introduced new Vector API as an incubating API.

This JEP improves the Vector API performance and other enhancements like support operations on characters, translating byte vectors to and from boolean arrays, etc.

14. JEP 415: Context-Specific Deserialization Filters

In Java, deserializing untrusted data is dangerous, read the OWASP – Deserialization of untrusted data and Brian Goetz – Towards Better Serialization.

Java 9, JEP 290 introduced serialization filtering to help prevent deserialization vulnerabilities.

14.1 The below example creates a custom filter using a pattern.

DdosExample.java

package com.mkyong.java17.jep415;

import java.io.Serializable;

public class DdosExample implements Serializable {
  @Override
  public String toString() {
      return "running ddos...!";
  }
}
JEP290.java

package com.mkyong.java17.jep415;

import java.io.*;

public class JEP290 {

  public static void main(String[] args) throws IOException {

      byte[] bytes = convertObjectToStream(new DdosExample());
      InputStream is = new ByteArrayInputStream(bytes);
      ObjectInputStream ois = new ObjectInputStream(is);

      // Setting a Custom Filter Using a Pattern
      // need full package path
      // the maximum number of bytes in the input stream = 1024
      // allows classes in com.mkyong.java17.jep415.*
      // allows classes in the java.base module
      // rejects all other classes !*
      ObjectInputFilter filter1 =
              ObjectInputFilter.Config.createFilter(
                  "maxbytes=1024;com.mkyong.java17.jep415.*;java.base/*;!*");
      ois.setObjectInputFilter(filter1);

      try {
          Object obj = ois.readObject();
          System.out.println("Read obj: " + obj);
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
  }

  private static byte[] convertObjectToStream(Object obj) {
      ByteArrayOutputStream boas = new ByteArrayOutputStream();
      try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
          ois.writeObject(obj);
          return boas.toByteArray();
      } catch (IOException ioe) {
          ioe.printStackTrace();
      }
      throw new RuntimeException();
  }

}

Output

Terminal

  Read obj: running ddos...!

The below example will reject all classes in package com.mkyong.java17.jep415.*:


  byte[] bytes = convertObjectToStream(new DdosExample());

  ObjectInputFilter filter1 =
        ObjectInputFilter.Config.createFilter(
          "!com.mkyong.java17.jep415.*;java.base/*;!*");  

Rerun it; this time, we will not be able to deserialize the object.

Terminal

  Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED
    at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1412)

14.2 The below example creates a deserialization filter to reject all classes that extended JComponent.

JComponentExample.java

package com.mkyong.java17.jep415;

import javax.swing.*;
import java.io.Serializable;

public class JComponentExample extends JComponent implements Serializable {
}
JEP290_B.java

package com.mkyong.java17.jep415;

import javax.swing.*;
import java.io.*;

public class JEP290_B {

    public static void main(String[] args) throws IOException {

        byte[] bytes = convertObjectToStream(new JComponentExample());
        InputStream is = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(is);

        ois.setObjectInputFilter(createObjectFilter());

        try {
            Object obj = ois.readObject();
            System.out.println("Read obj: " + obj);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // reject all JComponent classes
    private static ObjectInputFilter createObjectFilter() {
        return filterInfo -> {
            Class<?> clazz = filterInfo.serialClass();
            if (clazz != null) {
                return (JComponent.class.isAssignableFrom(clazz))
                        ? ObjectInputFilter.Status.REJECTED
                        : ObjectInputFilter.Status.ALLOWED;
            }
            return ObjectInputFilter.Status.UNDECIDED;
        };
    }

    private static byte[] convertObjectToStream(Object obj) {
        ByteArrayOutputStream boas = new ByteArrayOutputStream();
        try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
            ois.writeObject(obj);
            return boas.toByteArray();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        throw new RuntimeException();
    }

}

Output

Terminal

Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED
at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1412)
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2053)
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1907)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2209)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1742)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:514)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:472)
at com.mkyong.java17.jep415.JEP290_B.main(JEP290_B.java:17)

14.3 Java 17 added allowFilter and rejectFilter for ObjectInputFilter interface to create deserialization filters faster.


  allowFilter(Predicate<Class<?>>, ObjectInputFilter.Status)

  rejectFilter(Predicate<Class<?>>, ObjectInputFilter.Status)

For the above example in 14.2, now we can refactor the code like below:


    // Java 9
    private static ObjectInputFilter createObjectFilter() {
        return filterInfo -> {
            Class<?> clazz = filterInfo.serialClass();
            if (clazz != null) {
                return (JComponent.class.isAssignableFrom(clazz))
                        ? ObjectInputFilter.Status.REJECTED
                        : ObjectInputFilter.Status.ALLOWED;
            }
            return ObjectInputFilter.Status.UNDECIDED;
        };
    }

    // Java 17
    // reject all JComponent classes
    ObjectInputFilter jComponentFilter = ObjectInputFilter.rejectFilter(
            JComponent.class::isAssignableFrom,
            ObjectInputFilter.Status.UNDECIDED);
    ois.setObjectInputFilter(jComponentFilter);

14.4 Back to Java 17, this JEP 415 introduced a concept of filter factory, a BinaryOperator, to choose differents deserialization filters dynamically or context-specific. The factory determines how to combine the two filters or replace the filter.

Below is a Java 17 filter factory example of combining two deserialize filters.

JEP415_B.java

package com.mkyong.java17.jep415;

import java.io.*;
import java.util.function.BinaryOperator;

public class JEP415_B {

  static class PrintFilterFactory implements BinaryOperator<ObjectInputFilter> {

      @Override
      public ObjectInputFilter apply(
              ObjectInputFilter currentFilter, ObjectInputFilter nextFilter) {

          System.out.println("Current filter: " + currentFilter);
          System.out.println("Requested filter: " + nextFilter);

          // Returns a filter that merges the status of a filter and another filter
          return ObjectInputFilter.merge(nextFilter, currentFilter);

          // some logic and return other filters
          // reject all JComponent classes
          /*return filterInfo -> {
              Class<?> clazz = filterInfo.serialClass();
              if (clazz != null) {
                  if(JComponent.class.isAssignableFrom(clazz)){
                      return ObjectInputFilter.Status.REJECTED;
                  }
              }
              return ObjectInputFilter.Status.ALLOWED;
          };*/

      }
  }

  public static void main(String[] args) throws IOException {

      // Set a filter factory
      PrintFilterFactory filterFactory = new PrintFilterFactory();
      ObjectInputFilter.Config.setSerialFilterFactory(filterFactory);

      // create a maxdepth and package filter
      ObjectInputFilter filter1 =
              ObjectInputFilter.Config.createFilter(
                  "com.mkyong.java17.jep415.*;java.base/*;!*");
      ObjectInputFilter.Config.setSerialFilter(filter1);

      // Create a filter to allow String.class only
      ObjectInputFilter intFilter = ObjectInputFilter.allowFilter(
              cl -> cl.equals(String.class), ObjectInputFilter.Status.REJECTED);

      // if pass anything other than String.class, hits filter status: REJECTED
      //byte[] byteStream =convertObjectToStream(99);

      // Create input stream
      byte[] byteStream =convertObjectToStream("hello");
      InputStream is = new ByteArrayInputStream(byteStream);
      ObjectInputStream ois = new ObjectInputStream(is);

      ois.setObjectInputFilter(intFilter);

      try {
          Object obj = ois.readObject();
          System.out.println("Read obj: " + obj);
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
  }

  private static byte[] convertObjectToStream(Object obj) {
      ByteArrayOutputStream boas = new ByteArrayOutputStream();
      try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
          ois.writeObject(obj);
          return boas.toByteArray();
      } catch (IOException ioe) {
          ioe.printStackTrace();
      }
      throw new RuntimeException();
  }

}

Output

Terminal

Current filter: null
Requested filter: com.mkyong.java17.jep415.*;java.base/*;!*
Current filter: com.mkyong.java17.jep415.*;java.base/*;!*
Requested filter: predicate(
    com.mkyong.java17.jep415.JEP415_B$$Lambda$22/0x0000000800c01460@15aeb7ab,
      ifTrue: ALLOWED, ifFalse:REJECTED)
Read obj: hello

Further Reading

Please read below links for more deserialization filters examples:

Download Source Code

$ git clone https://github.com/mkyong/core-java

$ cd java-17

References

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
7 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Thomas
2 years ago

I ‘m using java 8 in 2021 😀

thanglv
1 year ago
Reply to  Thomas

me too

Maximiliano
1 year ago
Reply to  Thomas

Same here, to date the best version

Edward
2 years ago
Reply to  Thomas

Me too 🤣

Dragos
2 years ago
Reply to  Edward

Same here

Mohi.159
2 years ago
Reply to  Dragos

and me :))

LUCIANO ELIAS
1 year ago

I’m happy to see tutorials like this, very grateful