What is new in Java 14 (for Developers)

04 Apr 2020
12 mins read

Java JDK 14 was released on March 2020 as part of the new release cycle (a new Java version every 6 months). In this article we will learn what are the new features available for developers.

What’s new in Java 14?

Java JDK 14 is one of the releases I’ve personally been waiting for. It includes Switch Expressions (introduced in Java 12) - now as a complete feature, Text Blocks, Pattern Matching for instanceof (Preview) and my two favorite features: Helpful NullPointerExceptions and Records (Preview). Althought JDK 14 is not LTS (Long Term Support) it’s exciting to see the language evolving with features will have an impact in our daily professional lives as developers.

Below is a list of all features included in Java 14 (from Oracle blog):

Let’s take a look at Switch Expressions, new features from Text Blocks, Helpful NullPointerExceptions, Pattern Matching for instanceof and the new Records.

Before you try these new features, don’t forget to download and install the latest JDK. Also, don’t forget to download the latest version of your favorite IDE (latest Eclipse version or IntelliJ IDEA 2020.1).

Pattern Matching for instanceof (from JEP 305 - Preview Feature)

Consider the following code, and nothe the instanceof code inside the equals method:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (obj instanceof Person) {
            Person person = (Person) obj;
            return age == person.age &&
                   name.equals(person.name);
        }
        return false;
    }
}

Code such as Person person = (Person) obj is very common in Java projects. Pattern Matching for instanceof allows us to simplify this code. After refactoring with Java 14, this is what we have:

if (obj instanceof Person person) { // we can declare the variable person here
    // Person person = (Person) obj;
    return age == person.age &&
            name.equals(person.name);
}

Although this is a very simple change, the code is more concise. And any refactoring we can do to improve our code, we’ll take it! :)

Text Blocks - String Literals (from JEP 368 - Second Preview)

A text block is a multi-line string literal that avoids the need for most escape sequences, automatically formats the string in a predictable way, and gives the developer control over format when desired.

The first preview of this feature was introduced in Java 13.

Below is a snippet comparing how we used to declare multiple line string and from first preview in Java 13:

private static void textBlocks() {

  String beforeQuery = "update products\n" +
                        "    set quantityInStock = ?\n" +
                        "    ,modifiedDate = ?\n" +
                        "    ,modifiedBy = ?\n" +
                        "where productCode = ?\n";

  String updateQuery = """
          update products
              set quantityInStock = ?
              ,modifiedDate = ?
              ,modifiedBy = ?
          where productCode = ?
          """;
  System.out.print(updateQuery);
}

The second preview of this feature introduced two escape sequence we can use.

First, the \ escape sequence explicitly suppresses the insertion of a newline character.

The \s escape sequence can be used in both text blocks and traditional string literals.

Applying these two new escape sequence:

String updateQuery2 = """
        update products \
            set quantityInStock = ?
            ,modifiedDate = ?
            ,modifiedBy = ?  \s \
        where productCode = ?
        """;
System.out.println(updateQuery2);

We’ll have the following output:

update products set quantityInStock = ?
    ,modifiedDate = ?
    ,modifiedBy = ?  where productCode = ?

These two new escape sequence give us a little bit more flexiblity to write readable code without comprimising the way we want the output to be!

Switch Expressions (from JEP 361 - Complete Feature)

Switch Expressions are now much more optimized (syntax wise) than before. I have published details in these two blogs posts:

To summarize, we can use yield to return a value from switch instead of break:

private String switchJava13(DAY_OF_WEEK dayOfWeek) {
  return switch (dayOfWeek) {
      case MONDAY:
      case TUESDAY:
      case WEDNESDAY:
      case THURSDAY:
      case FRIDAY:
          yield "Weekday";
      case SATURDAY:
      case SUNDAY:
          yield "Weekend";
  };
}

We can also use the case value -> syntax (introduced in Java 12 as preview):

private String enhancedSwitchCase(DAY_OF_WEEK dayOfWeek) {
    return switch (dayOfWeek) {
        case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
        case SATURDAY, SUNDAY -> "Weekend";
    };
}

Helpful NullPointerExceptions (from JEP 358)

This is one of my favortite JEPs this release!

Given the code below, we’re going to get a NullPointerException because matrix[1][0] is null.

private static void helpfulNullPointerExceptions(){
    String[][] matrix = new String[5][5];
    matrix[1] = new String[5];

    if (matrix[1][0].toUpperCase().equals("S")) {
      // some code here
    }
}

If we run code above with Java 13 or before, we’ll get the following exception:

Exception in thread "main" java.lang.NullPointerException
	at com.loiane.Java14Features.helpfulNullPointerExceptions(Java14Features.java:33)
	at com.loiane.Java14Features.main(Java14Features.java:20)

This stack trace is helpful, but not that helpful. We don’t know if the exception was thrown because matrix[1] is null or matrix[1][0], and to find it out it requires some debugging.

This JEP introduces a JVM parameter that can show the exact reason of this exception. By default, this feature is set to false, but we can enable it by adding -XX:+ShowCodeDetailsInExceptionMessages to the JVM options as showed below:

According to the JEP details: “The option will first have default ‘false’ so that the message is not printed. It is intended to enable code details in exception messages by default in a later release.”.

Executing the code again, we’ll get the helpfull NullPointerException:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because "matrix[1][0]" is null
	at com.loiane.Java14Features.helpfulNullPointerExceptions(Java14Features.java:33)
	at com.loiane.Java14Features.main(Java14Features.java:20)

Much better!

Records (from JEP 359 - Preview Feature)

This is another one of my favorites JEPs from this release!

From the JEP description: “Records are a new kind of type declaration in the Java language. Like an enum, a record is a restricted form of class. It declares its representation, and commits to an API that matches that representation. Records give up a freedom that classes usually enjoy: the ability to decouple API from representation. In return, records gain a significant degree of concision.”.

When we create a class to represent something in Java, we usually declare all the atributes/properties, then create the getters and setters, contructors, and methods toString, equals, hashCode. Altough the IDE can create the methods for us, when reading the code later, it’s too many lines, and sometimes what we need is something very simple just to represent something.

There is an alternative to declutter the classes, which is done by project Lombok. Some developers like it, others don’t like it.

However, with the new Records, decluttering our code into a more elegant code is possible throuhg records!

Let’s take a look how we can declare a Record:

public record Product(String name,int quantity,double price) {}

That’s awesome! Now, this is what we get once the code is compiled:

public final class Product extends java.lang.Record {
    private final java.lang.String name;
    private final int quantity;
    private final double price;

    public Product(java.lang.String name, int quantity, double price) { /* compiled code */ }

    public java.lang.String toString() { /* compiled code */ }

    public final int hashCode() { /* compiled code */ }

    public final boolean equals(java.lang.Object o) { /* compiled code */ }

    public java.lang.String name() { /* compiled code */ }

    public int quantity() { /* compiled code */ }

    public double price() { /* compiled code */ }
}

All properties are private and final, which makes our properties immutable. We don’t get setters, and the getters don’t have the get or is prefix. We also get by default the methods toString, hashCode and equals. Our Record is compiled into a class that extends the class Record and it’s also a final class, meaning we cannot declare an abstract Record. And all the properties are declared within parenthesis (), meaning we cannot declare instance properties inside a Record.

Because of this new type, some new methods were also added into the Java API. The Class class has two methods for the new Record feature: isRecord() and getRecordComponents() (used to retrive details of annotations and the generic type),. The ElementType enumeration has a new constant for Records: RECORD_TYPE.

If we need to, we can override the default contructor to add any specific logic required:

public record Product(String name,int quantity,double price) {
    public Product {
        if (quantity < 1) {
            throw new IllegalArgumentException("Product needs to have at least 1 quantity");
        }
    }
}

The constructor can also declare arguments/parameters:

public record Product(String name,int quantity,double price) {
    public Product(String name, int quantity, double price) {
        if (quantity < 1) {
            throw new IllegalArgumentException("Product needs to have at least 1 quantity");
        }
        this.name = name;
        this.quantity = quantity;
        this.price = price;
    }
}

But that’s now all we can do with a Record. We can also declare static properties and methods:

public record Product(String name,int quantity,double price) {
    private static int COUNT = 0;

    static int count() {
        return COUNT;
    }
}

A Record cannot extend another class (as the compiled code is a class that exteds Record and Java does not allow multiple inheritance with classes), but a Record can implement interfaces:

public record Product(String name,int quantity,double price)
  implements Comparable<Product> {
    @Override
    public int compareTo(Product o) {
        return 0; // code here
    }
}

And finally, we can also add annotations to the Record’s properties:

public record Product(
        @NotNull String name,
        int quantity,
        @Min(1) double price) {}

Personally, I can’t wait for this JEP to be a complete feature so I can start refactoring existing projects!

Conclusion

This was a big release for Java in number of JEPs, with some important features for developers. Now let’s wait for Java 15 (September 2020) with more new features!

View the full source code on GitHub.

References:

Happy Coding!