New features of Java 14 in practice

Andrey Kovalev
Analytics Vidhya
Published in
4 min readMay 21, 2020
Photo by Azharul Islam on Unsplash

The recent release of JDK 14 has brought a total of 16 main enhancements. In this article, I’ll walk you through the most interesting ones, mostly related to language support.

1.Records (Preview feature)
Probably the most impressive part of a new release. Those are a new kind of type declaration in Java.

public record User(long id, String name) {}

With only one line we get a new final class, making all of its fields final as well. At compile time the record will automatically generate the boilerplate constructors, public get, equals(), hashCode(), toString(). There won’t be any setters as all the fields are final.

We can start using a new User record like we’re used to using classes:

public static void main(String[] args) {
User user = new User(1L, "Mark");
user.id();
user.name();
}

Records serve as a data carrier and have strict limitations for supporting immutability out of the box.

They cannot be abstract, extend any other class even its implicit superclass. That is because the record’s API is defined by the state it maintains and can’t allow its composing class to modify it.

For the same reason a recordcan’t have a native methods declaration, because it can’t depend on external logic.

public native String getSystemTime(); //compilation error

static {
System.loadLibrary("nativedatetimeutils");
}

A recordcannot explicitly declare instance fields or have setter methods. Only the record’s header defines the state of a record value. That’s why the code below won’t compile:

void setId(long id){
this.id = id; //compilation error
}

A record can be serialized into JSON using one of your favorite java libraries like Gson or Jackson:

Gson gson = new Gson();

String userJson = gson.toJson(user);
System.out.println(userJson); //outputs {"id":1,"name":"Mark"}

And deserialized back:

User newUser = gson.fromJson(userJson, User.class);
System.out.println(newUser); //outputs User[id=1, name=Mark]

One more interesting fact the superclass of each record is Record itself:

Class<?> superclass = user.getClass().getSuperclass();
System.out.println(superclass); //class java.lang.Record

Unfortunately, records can’t be used as lightweight persistence domain objects each representing a table in a relational database. That’s because JPA requires a non-arguments constructor which records don’t have. JPA provider has to get and set the fields of the entity using reflection which is also impossible as records’ fields are final. So the following code won’t compile:

@Entity
public record User(long id, String name) { //compilation error

}

You will need to keep using old known Lombok:

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Size(max = 40)
private String name;
}

2.Pattern Matching for instanceof (Preview)

Before Java 14 if you wanted to do instanceof check then to cast the object to a variable you’d do the following:

if (obj instanceof User) {
String s = (User) obj;
// use s
}

With Java 14 this can be simplified to a single line:

if (obj instanceof String s) {
// can use s here
}

Though you might be tempted to try a new feature in most of the cases you should still avoid using instanceof in production code. Good usage of polymorphism should be preferred over the basic use of conditional.

3.Text Blocks (Second Preview)

This feature adds text blocksto the Java language. A text block is a string literal that consists of multiple lines. Using text blocks helps to avoid most of the escape sequences, string concatenations. Overall it simplifies the process of writing programs by making it easy to express strings that take several lines of source code.

Before Java 14:

String someHtml = "<html>\n" +
" <body>\n" +
" <p>Hello World</p>\n" +
" </body>\n" +
"</html>\n";

With Java 14:

String java14 = """
<html>
<body>
<p>Hello World</p>
</body>
</html>
""";

4.Switch Expressions

The Switch Expressions was a preview feature in Java 12 and Java 13, and since Java 14 it has become a standard language feature. A new switch can be used as an expression using arrow syntax:

switch (article.state()) {
case DRAFT -> System.out.println(1);
case PUBLISHED -> System.out.println(2);
case UNKNOWN -> System.out.println(3);
}

Before Java 14:

switch (article.state()) {
case DRAFT:
System.out.println(1);
break;
case PUBLISHED:
System.out.println(2);
break;
case UNKNOWN:
System.out.println(3);
break;
}

And now can yield/return the value

int result = switch (article.state()) {
case DRAFT -> 6;
case PUBLISHED -> 7;
case UNKNOWN -> 8;
};

Before Java 14:

int result;
switch (article.state()) {
case DRAFT:
result = 6;
break;
case PUBLISHED:
result = 7;
break;
case UNKNOWN:
result = 8;
break;
default:
throw new UnsupportedOperationException();
}

5.Helpful NullPointerExceptions

This feature will help to track and resolve NullPointerExceptions produced by JVM faster. Before Java 14 those messages weren’t informative at all:

public static void main(String[] args) {
User user = new User(1L, null);

System.out.println(toUpperCase(user));
}

private static String toUpperCase(User user) {
return user.name().toUpperCase(); // produces NPE
}

The message will look like:

“Exception in thread “main” java.lang.NullPointerException
at demo.Main.main(Main.java:16)”

With a new enhancement adding “XX:+ShowCodeDetailsInExceptionMessages” to VM options will make the message look like:

Exception in thread “main” java.lang.NullPointerException: Cannot invoke “String.toUpperCase()” because the return value of “test.User.name()” is null
at demo.Main.toUpperCase(Main.java:20)
at demo.Main.main(Main.java:16)

Summary

The overview of the newest features in Java 14 has come to its end. You now have the knowledge and hands-on experience with the newest features in Java world. I hope you enjoyed this article and found it useful. Keep practicing as tomorrow’s battle is won during today’s practice!

--

--