Java 17 : what’s new ?

Java 17 : what’s new ?

Now that Java 17 is features complete (Rampdown Phase Two at the day of writing), it’s time to walk throught all the functionalities that brings to us, developers, this new version.

This article is part of a series on what’s new on the last versions of Java, for those who wants to read the others, here are the links : Java 16, Java 15, Java 14, Java 13, Java 12, Java 11Java 10, and Java 9.

This release doesn’t contain many JEPs, and therefore not many important new features. On the other hand, it brings some nice additions, and its LTS (Long Term Support) status makes it an important release.

JEP 406: Pattern Matching for switch (Preview)

This is probably the biggest news of this release, the pattern matching arrives in the switch, in preview. We can now make a switch on the type of a variable (including enum, record and array), and extract a local variable of the corresponding type.

The JEP gives the following example with an if/else chain:

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;
}

Which can now be re-written with a switch expression:

static String formatterPatternSwitch(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();
    };
}

In addition to supporting pattern matching, the switch now allows you to define a special null case (in its two forms: statement or expression). Previously, a null switch variable resulted in a NullPointerException. In its new form, we can add a null case to handle nulls within the switch. Without case null, the old behavior is kept, and a NullPointerException will be thrown.

If we take the previous example, this gives us:

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case null -> "null";
        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();
    };
}

The big advantage is that it avoids having to do a defensive test before the switch, and allows to include in it the null value as any other possible values of our variable.

And it doesn’t stop there, the switch has been enhanced with guards that allow to include a condition to the case. The following example from the JEP shows its use. By adding a guard to the Triangle case: case Triangle t && (t.calculateArea() > 100), we can create two cases: one for large triangles, and another for small ones.

static void testTriangle(Shape s) {
    switch (s) {
        case Triangle t && (t.calculateArea() > 100) -> System.out.println("Large triangle");
        case Triangle t -> System.out.println("Small triangle");
        default -> System.out.println("Non-triangle");
    }
}

More information in the JEP-406.

JEP 356: Enhanced Pseudo-Random Number Generators

The JEP-356 provides a new interface RandomGenerator, and a factory RandomGeneratorFactory, which provide access to a random number generator implementation. The existing generators: Random, SecureRandom, SplittableRandom and ThreadLocalRandom; now implement this interface which adds, among other things, access to a random number stream (RandomGenerator::doubles(), RandomGenerator::ints(), …).

New random number generation algorithms have been implemented, more secure and more powerful (but they are not thread-safe anymore), they are intended to replace the old ones.

Here are some examples of instantiation of random number generators:

// the old Random generator
RandomGenerator rng1 = RandomGeneratorFactory.of("Random").create(42);
// the default random generator, currently jdk.random.L32X64MixRandom but this can change
RandomGenerator rng2 = RandomGeneratorFactory.getDefault().create(42);
// shortcut for the default
RandomGenerator rng3 = RandomGenerator.getDefault(); 
// stream all available generators and display their names
RandomGeneratorFactory.all().forEach(generator -> System.out.println(generator.name());

More information in the JEP-356 or in this very complete article of
Michael Bien : Java 17’s Enhanced Pseudo-Random Number Generators.

Features that go from preview to standard

In Java 17, only one feature goes from preview to standard: the Sealed Classes (JEP-409), you can refer to my previous articles for more information.

Features that stay in preview

The following features remain in preview (or incubator module).

For details on these, you can refer to my previous articles.

  • JEP-414 – Vector API: second incubation of this feature that incorporates improvements within the API and better performance.
  • JEP-412 – Foreign Function & Memory API: new incubator for these two features that are now linked (they use each other) within the same incubator.

A new port of the JVM

Java 17 adds support for the macOS/AArch64 (aka Apple Silicon M1) architecture. More information about this in the JEP-391.

HexFormat

The class java.util.HexFormat allows conversion of primitive type, byte array, or char array to hex string and vice versa.

HexFormat.of().toHexDigits(127); // "7f"
HexFormat.of().fromHexDigits("7f"); // 127

More information in the ticket JDK-8251989.

InstantSource

Testing code containing date manipulation has always been a challenge, especially if it uses and abuses System.currentTimeMillis(), LocalDateTime.now(), and other date initialization with the current date.

To facilitate the testability of this kind of code, a new interface has been added to the JDK: InstantSource, with a single implementation Clock. The purpose of the InstantSource interface is to be an Instant factory. Instead of creating an Instant with the current date, you create it from the InstantSource. A test can then use an InstantSource at a fixed date instead of today’s date.

Imagine the following code:

  public class MyBean {
    private InstantSource source;  // dependency inject
    ...
    public void process(Instant endInstant) {
      if (source.instant().isAfter(endInstant) {
        ...
      }
    }
  }

In normal operation, the InstanSource is initialized via InstantSource.system(), and in testing via InstantSource.fixed(LocaDateTime.of(//the hardcoded date time)).

Miscellaneous

Various JDK additions:

  • Map.Entry.copyOf(Map.Entry): allows to create a copy of a Map entry that is not connected to the existing Map.
  • Process.inputReader(), Process.outputWritter(), Process.errorReader(): allows access to the input, standard output, and error output of a process via a Reader or a Writer.

Deprecation and encapsulation

Java 17 sees the deprecation for removal of the Applets API via the JEP-398, this one hasn’t been in use for many years, so it hasn’t caused much of a stir.

Java 17 also sees the deprecation of the Security Manager for removal via the JEP-411. There was a lot of debate around this announcement, with admittedly a bit of drama, such as when Apache Netbeans announced that it couldn’t support Java 17 when all it needed to do was change a few lines of code …
The removal of the Security Manager was announced, because it is complex to maintain, expensive in terms of performance, and does not provide the necessary security in the face of current challenges. It was created to secure applets which, by definition, execute untrusted code, and therefore no longer makes sense in a JVM that would no longer contain the Applet API.
For more info on its removal, you can read this InfoQ article or this reddit thread (I really enjoyed pron98’s responses).

Finally, the JEP 403: Strongly Encapsulate JDK Internals switched the JDK to strict encapsulation of its internal classes. This is the end of what was started with Java 9 and the modularization of the JDK.
Specifically, the encapsulation mode had moved from --illegal-access=permit in Java 15 to --illegal-access=deny in Java 16 with the ability to change the configuration option. With Java 17, --illegal-access disappears and access to internal JDK classes (excluding Unsafe) is no longer possible.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.