A roundup of the new language features of JDK14

In the midst of the corona COVID-19 crisis, one might have missed the JDK 14 release of March 17. This release of the JDK comprises a total of 16 features.

This article is a TLDR-version of what I think are the most interesting features of this release of the JDK. It is therefore opinionated and I gladly encourage you to read the full release notes should you be interested.

New features

First, let’s take a look at the new features that have been made final in JDK 14.

JFR event streaming

Not all developers may be aware of this, but our JDK contains the Java Flight Recorder (JFR) that can be used to collect diagnostic and profiling data about a running java application. Until now, the only option to work with this data was dumping the recorded state to disk and parse the resulting log file.

Now, there is a streaming facility through which users can read recording data directly by registering handlers on the jdk.jfr.consumer.RecordingStream. This example will print the total CPU load of our machine to the console, once every second:

try (var r = new RecordingStream()) {
    r.enable(“jdk.CPULoad”).withPeriod(Duration.ofSeconds(1));
    r.onEvent(“jdk.CPULoad”, e -> System.out.println(e.getFloat(“machineTotal”));
    r.start();
}

 

This could be useful in developing self diagnosing and adaptive micro services.

Helpful NullPointerExceptions

There are two kinds of developers, those that hate NullPointerExceptions and those that really hate NullPointerExceptions. Although optionals have alleviated our suffering, the NullPointerException or NPE for short is here to stay and that’s something we just have to deal with.

JDK 14 can log more helpful NPE error messages by passing JVM argument -XX:+ShowCodeDetailsInExceptionMessages when running our application. Take this code for example:

public static void main(String args[]) {
    Pizza pizza = null;
    pizza.addTopping(new Cheese());
}

 

This will throw an NPE when adding cheese to the non existing pizza:

Exception in thread "main" java.lang.NullPointerException
 at Application.main(Application.java:4)

 

With helpful NPEs turned on, we get a much more detailed error message:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Pizza.addTopping(Topping)" because "pizza" is null
 at Application.main(Application.java:4)

 

This behavior is intended to be enabled by default in a future release.

Switch expressions

This feature is really exciting. After first being proposed late 2017 and previews of this feature in JDK 12 and 13, it is made final and permanent in JDK 14.

Switch expressions give us a clear and concise way of writing switch statements using lambda syntax. An example:

String mask = switch (tmnt) {
    case LEONARDO -> “blue”;
    case MICHELANGELO -> “orange”;
    case DONATELLO -> “purple”;
    case RAPHAEL -> “red”;
};

 

Besides improved readability, there is also the benefit of not having support for case fall through. No more risk of unintended behavior due to a forgotten break statement.

Preview features

As usual, this release also comes with previews of functions to come in future releases.

Text blocks

This is a feature I’m particularly looking forward to. Unfortunately, as trivial as it may seem, it didn’t make the cut for JDK 14 and therefore remains in preview.

After feedback, two new escape sequences have been added to suppress new lines (‘\’) and preserve trailing whitespace (‘\s’).

Hopefully, this will be the last changes and from JDK 15 on we will be able to write multiline SQL queries in a readable manner:

String sql = """
    SELECT *
    FROM pizza p
        INNER JOIN pizza_topping pt ON pt.pizza_id = p.id
        INNER JOIN topping t ON t.id = pt.topping_id
    WHERE t.name = 'mozzarella cheese'
""";

 

Pattern matching for instanceof

Every now and then we write code that follows a type test pattern. We check if an object is of a certain type and conditionally cast the object to a reference of this type:

if (object instanceof Pizza) {
    Pizza p = (Pizza) object;
    p.eat();
}

 

With instanceof pattern matching, we are allowed to shorten this code by supplying the binding variable along with the predicate:

if (object instanceof Pizza p) {
    p.eat();
}

 

Now we can have our pizza and eat it too 😉

Records

A record is a new kind of type declaration. It is a restricted form of a class, like an enum. Its goal is to help us write simple but proper data carrier classes in a compact way, without having to write boilerplate code like accessors, equals(), hashCode(), etc.

For example, we can use a record to represent an address latitude and longitude coordinates for our pizza delivery service:

public record LatLong(float latitude, float longitude) {}

 

Records behave like normal classes and are immutable by default. All record components are implicitly final. If we’d written it as a normal class, LatLong would look something like this:

public final class LatLong {
    public final float latitude, longitude;

    public LatLong(float latitude, float longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public boolean equals(Object other) {
        if (!(other instanceof LatLong))
            return false;
        LatLong latlong = (LatLong) other;
        return latitude == latlong.latitude && longitude == latlong.longitude;
    }

    public int hashCode() {
        return 31 * (int) latitude * (int) longitude;
    }

    public String toString() {
        return String.format(“LatLong[latitude=%f, longitude=%f]”, latitude, longitude);
    }
}

 

One cool feature of records is because constructor parameters are inferred by the declaration of the record, we can omit them when declaring a custom constructor:

public record LatLong(float latitude, float longitude) {
    public LatLong {
        if (latitude < -90 || latitude > 90)
            throw new IllegalArgumentException(“Invalid latitude: “ + latitude);
        if (longitude < -180 || longitude > 180)
            throw new IllegalArgumentException(“Invalid longitude: “ + longitude);
    }
}

 

Finally, java.lang.Class is extended with methods isRecord() for checking whether a type is a record and getRecordComponents() which returns an array of RecordComponents that hold information about the record’s components (in this case latitude and longitude).

 

Rens Verhage

Rens Verhage is sinds maart 2020 in dienst als Senior Software Engineer bij Profit4Cloud. Rens heeft ruim 14 jaar ervaring met Java en is OCA / OCP gecertificeerd.