days
0
-18
0
hours
-1
-8
minutes
-2
-2
seconds
-3
-4
search
The new features of JDK 14 in a nutshell

Java – the fourteenth

Falk Sippach
Java 14

© Shutterstock / haveseen

„Languages must evolve, or they risk becoming irrelevant,“ Brian Goetz (Oracle) said in November 2019 during his presentation „Java Language Futures“ at Devoxx in Belgium. As Java Language Architect, he plays a major role in the fact that Java, despite its 25 years, is still far from being outdated. In this article, we’ll take a look at the innovations of JDK 14.

In recent years, Oracle has made some groundbreaking decisions. They include the new semi-annual release model with preview features and shorter release and feedback cycles for new features. The licensing model has been changed as well, with the Oracle JDK no longer being offered for free. This has boosted competition, so you can now get free OpenJDK distributions from various vendors, including Oracle. Since Java 11, it has been binary-compatible with the Oracle JDK and is under an open source license.

One and a half years ago, the last LTS version, Java 11, was released in the fall of 2019. Since then, the subsequent two major releases have only had a limited number of new features. The JDK incubator projects (Amber, Valhalla, Loom …), however, are working on many new ideas, so it is not surprising that the functional range of the just released JDK 14 is again significantly larger. And even if only few will use the new version in production, you should still take a look at the new features and give feedback on the preview functions early on. This is the only way to ensure that the new features are ready for production until finalization in the next LTS release, which will be released as Java 17 in the fall of 2021.

SEE ALSO: Java 14 – “It feels like the early days of Java.”

The following Java Enhancement Proposals (JEP) have been implemented. In this article, we will take a closer look at the topics that are interesting from a developer’s point of view.

  • JEP 305: Pattern Matching for instanceof (Preview)
  • JEP 343: Packaging Tool (Incubator)
  • JEP 345: NUMA-Aware Memory Allocation for G1
  • JEP 349: JFR Event Streaming
  • JEP 352: Non-Volatile Mapped Byte Buffers
  • JEP 358: Helpful NullPointerExceptions
  • JEP 359: Records (Preview)
  • JEP 361: Switch Expressions (Standard)
  • JEP 362: Deprecate the Solaris and SPARC Ports
  • JEP 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
  • JEP 364: ZGC on macOS
  • JEP 365: ZGC on Windows
  • JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination
  • JEP 367: Remove the Pack200 Tools and API
  • JEP 368: Text Blocks (Second Preview)
  • JEP 370: Foreign-Memory Access API (Incubator)

JEP 305: Pattern matching for instanceof

The concept of pattern matching has been used in various programming languages since the 1960s. Among the more modern examples are Haskell and Scala. A pattern is a combination of a predicate that matches a target structure and a set of variables within that pattern. These variables are assigned the corresponding contents if they match. The intention is the destructuring of objects, i.e. splitting them into their components.

Up until now, Java could only distinguish between the data types integer, string and enum in switch statements. With the introduction of switch expressions in Java 12, however, the first step towards pattern matching had been made. With Java 14, we can now additionally use pattern matching for the instanceof operator. Unnecessary casts are avoided and the reduced redundancy increases readability.

Before this, for example, you had to proceed as follows to check for an empty string or an empty collection:

boolean isNullOrEmpty( Object o ) {
  return == null ||
    instanceof String && ((String) o).isBlank() ||
    instanceof Collection && ((Collection) o).isEmpty();
}

Now you can assign the value directly to a variable when checking with instanceof, and execute further calls on it:

boolean isNullOrEmpty( Object o ) {
  return o == null ||
    o instanceof String s && s.isBlank() ||
    o instanceof Collection c && c.isEmpty();
}

The difference may seem marginal. However, the purists among the Java developers save a small but annoying redundancy.

Switch expressions were first introduced in Java 12 and 13, in both cases as a preview feature. They have now been finalized in JEP 361. This provides developers with two new syntax variants with shorter, clearer and less error-prone semantics. The result of the expression can be assigned to a variable or returned as value from a method (Listing 1).

String developerRating( int numberOfChildren ) {
  return switch (numberOfChildren) {
    case 0 -> "open source contributor";
    case 1, 2 -> "junior";
    case 3 -> "senior";
    default -> {
      if (numberOfChildren < 0) 
        throw new IndexOutOfBoundsException( numberOfChildren );
      yield "manager";
    }
  };
}

JEP 358: Helpful NullPointerExceptions

The unintentional access to empty references is also feared by Java developers. According to Sir Tony Hoare’s own statement, his invention of the zero reference was an error with consequences amounting to many billions of dollars. And that was only because it was so easy to implement during the development of the Algol language in the 1960s.

In Java, neither the compiler nor the runtime environment support the handling of zero references. These annoying exceptions can be avoided with various workarounds. The easiest way is to set the checks to zero. Unfortunately, this procedure is very cumbersome and we tend to forget it exactly when we need it. With the wrapper class Optional, included since JDK 8, you can explicitly tell the caller via the API that a value can be zero and it must react to it. So, you can no longer accidentally run into a null reference, but have to explicitly deal with the possibly empty value. This procedure is useful for return values of public interfaces, but also costs an extra indirection layer because you always have to unpack the actual value.

In other languages, auxiliary tools have long been built into syntax and compilers, such as the NullObjectPattern and the Safe Navigation operator (some?.method()) in Groovy. In Kotlin, it is possible to distinguish explicitly between types that may not be empty and others that may have null as a reference. We will have to live with NullPointerExceptions in Java also in the future. But, at least, Helpful NullPointerExceptions, which were introduced as a preview feature, make troubleshooting exceptions easier. In order to insert the necessary information when throwing a NullPointerException, you have to activate the option -XX:+ShowCodeDetailsInExceptionMessages at startup. If a value is zero in a call chain, you will receive a useful message:

man.partner().name()

Result: java.lang.NullPointerException: Cannot invoke "Person.name()" because the return value of "Person.partner()" is null

Lambda expressions require a special treatment. If, for example, the parameter of a lambda function is zero, you will by default receive the insufficient error message shown in Listing 2. To display the correct parameter name, the source code must be compiled with the -g:vars option. This is the result:

java.lang.NullPointerException: Cannot invoke "Person.name()" because "p" is null
Stream.of( man, woman )
  .map( p -> p.partner() )
.map( p -> p.name() )
.collect( Collectors.toUnmodifiableList() );

Result: java.lang.NullPointerException: Cannot invoke "Person.name()" because "<parameter1>" is null

Unfortunately, there is currently no indication for method references in case of an empty parameter:

Stream.of( man, woman )
  .map( Person::partner )
  .map( Person::name )
  .collect( Collectors.toUnmodifiableList() )
Result: java.lang.NullPointerException

But if you place each stream method call into a new line, as in this example, the troublesome line of code can be narrowed down very quickly. NullPointerExceptions have also been challenging with automatic boxing/unboxing. If the compiler parameter -g:vars is activated here as well, you will also get the new helpful error message (Listing 3).

int calculate() {
  Integer a = 2, b = 4, x = null;
  return a + b * x;
}
calculate();
Result: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "x" is null

JEP 359: Records

The probably most exciting and at the same time most surprising innovation is the introduction of record types. They were implemented relatively late in the release of Java 14 and are a restricted form of class declaration, similar to enums. Records were developed within the Valhalla project. There are certain similarities to Data Classes in Kotlin and Case Classes in Scala. The compact syntax could make libraries like Lombok obsolete in the future. Kevlin Henney also sees the following advantage: “I think one of the interesting side effects of the Java record feature is that, in practice, it will help expose how much Java code really is getter/setter-oriented rather than object-oriented.” The simple definition of a person with two fields can be seen here:

public record Person( String name, Person partner ) {}

An extended variant with an additional constructor, so that only the field name is mandatory, can be implemented as well:

public record Person( String name, Person partner ) {
  public Person( String name ) { this( name, null ); }
  public String getNameInUppercase() { return name.toUpperCase(); }
}

The compiler generates an immutable class, which, in addition to the two attributes and its own methods, also contains the implementations for the accessors (no getters!), the constructor, equals/hashcode and toString (Listing 4).

public final class Person extends Record {
  private final String name;
  private final Person partner;
  
  public Person(String name) { this(name, null); }
  public Person(String name, Person partner) { this.name = name; this.partner = partner; }

  public String getNameInUppercase() { return name.toUpperCase(); }
  public String toString() { /* ... */ }
  public final int hashCode() { /* ... */ }
  public final boolean equals(Object o) { /* ... */ }
  public String name() { return name; }
  public Person partner() { return partner; }
}

The usage behaves as expected. You cannot tell from the caller that record types are instantiated (Listing 5).

var man = new Person("Adam");
var woman = new Person("Eve", man);
woman.toString(); // ==> "Person[name=Eve, partner=Person[name=Adam, partner=null]]"

woman.partner().name(); // ==> "Adam"
woman.getNameInUppercase(); // ==> "EVE"

// Deep equals
new Person("Eve", new Person("Adam")).equals( woman ); // ==> true

By the way, records are not classic Java Beans, since they do not contain real getters. However, you can access the member variables using the methods of the same name. Records can also contain annotations or Javadocs. Furthermore, you can declare static fields, methods, constructors, or instance methods in the body. It is not permitted to define additional instance fields outside the record header.

JEP 368: Text Blocks

Originally planned as raw string literals for Java 12, Java 13 introduced a lighter version in the form of multi-line strings called text blocks. Especially for HTML templates and SQL scripts, they greatly increase readability (Listing 6).

// Without Text Blocks
String html = "<html>\n" +
              "    <body>\n" +
              "        

Hello, Escapes

\n" +
              "    </body>\n" +
              "</html>\n";

// With Text Blocks
String html = """
              <html>
                  <body>
                      

Hello, Text Blocks

                  </body>
              </html>""";

Two new escape sequences have been added as well, with which you can adjust the formatting of a text block. For example, if you want to use a line break which should not appear explicitly in the output, you can simply insert a \ (backslash) at the end of the line. This gives you a string with one long line, but you may use line breaks in the source code for clarity (Listing 7).

String text = """
                Lorem ipsum dolor sit amet, consectetur adipiscing \
                elit, sed do eiusmod tempor incididunt ut labore \
                et dolore magna aliqua.\
                """;
// instead of
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
                 "elit, sed do eiusmod tempor incididunt ut labore " +
                 "et dolore magna aliqua.";

The new escape sequence \s is translated to a space. This can be used, for example, to ensure that blanks at the end of a line are not automatically truncated (trimmed) and to obtain a fixed character width per line:

String colors = """
    red  \s
    green\s
    blue \s
    """;

What else is new?

Aside from the described features, which are mainly interesting for developers, there are several other changes. In JEP 352, the FileChannel API was extended to allow the creation of MappedByteBuffer instances. In contrast to the volatile memory, the RAM, these work on non-volatile data storage (NVM, non-volatile memory). However, the target platform is Linux x64. A lot has also happened regarding garbage collection. The Concurrent Mark Sweep (CMS) garbage collector has been removed. Therefore, the ZGC is now also available for macOS and Windows.

For critical Java applications, it is recommended to activate the flight recording function in production. The following command starts a Java application with Flight Recording and writes the information to recording.jfr, always keeping the data of one day:

java \
-XX:+FlightRecorder \
-XX:StartFlightRecording=disk=true, \
filename=recording.jfr,dumponexit=true,maxage=1d \
-jar application.jar

Usually, you can then read and analyze the data with the tool JDK Mission Control (JMC). Another new feature of JDK 14 is that you can also query the events asynchronously from the application (Listing 8).

import jdk.jfr.consumer.RecordingStream;
import java.time.Duration;

try ( var rs = new RecordingStream() ) {
  rs.enable( "jdk.CPULoad" ).withPeriod( Duration.ofSeconds( 1 ) );
  rs.onEvent( "jdk.CPULoad", event -> {
    System.out.printf( "%.1f %% %n", event.getFloat( "machineTotal" ) * 100 );
  });
  rs.start();
}

In JDK 8, we had the tool javapackager, but unfortunately it was removed from Java in version 11 along with JavaFX. In Java 14, the successor jpackage is now introduced (JEP 343: Packaging Tool), with which we can once again create independent Java installation files. Their basis is the Java application including a runtime environment. The tool uses this input to build an executable binary artifact that contains all dependencies (formats: msi, exe, pkg in a dmg, app in a dmg, deb and rpm).

SEE ALSO: Pattern Matching for instanceof in Java 14

Conclusion

Java is not dead, long live Java! The biannual OpenJDK releases benefit both the language and the platform. This time, there were even considerably more new features than in Java 12 and 13. And there are still many features waiting to be implemented in future versions. So, we Java developers won’t get bored, and the future continues to look bright. In September 2020, we can expect the arrival of Java 15.

Author
Falk Sippach
Falk Sippach has twenty years of experience with Java and works for OIO – the Java experts at Trivadis – as a trainer, software developer and architect. He regularly publishes blog posts and technical articles and speaks at conferences. In his adopted hometown Darmstadt, he is one of the organizers of the local Java User Group.

Leave a Reply

avatar
400