New features of Java 16 and Java 17

The Java platform is evolving more rapidly than ever. Let's explore what this year has in store for us in terms of Java innovation for Java 16 and Java 17.

Author: Sander Mak


“Java? Isn’t that the slow-moving enterprise language from a previous era?”

While this is a somewhat understandable sentiment, the truth couldn’t be more different. With a twice-yearly Java Development Kit (JDK) release schedule starting with Java 10 back in 2018, the Java platform is evolving more rapidly than ever. Let’s explore what this year has in store for us in terms of Java innovation!

What Java updates look like

Changes to Java can generally be divided into three major areas:

  • The Java programming language
  • The Java core libraries
  • The Java Virtual Machine (JVM)

Almost every release offers improvements in each of these areas. Language and library changes usually have the highest visibility to developers. But don’t underestimate the compounding effect of JVM improvements delivered with every JDK release. These can include better garbage collection algorithms, performance improvements and strengthened security. All these JVM improvements make your existing Java applications run better by just upgrading, without even changing a single line of code.

In March 2021, Java 16 was released. We can expect Java 17 in September. So let’s see what we already have and what we can expect this year.

What’s new with Java 16

Two major language changes were shipped with Java 16: Records and Pattern Matching for instanceof. Records especially can become a gamechanger for many Java codebases.

Often, we’d like classes to only represent some data—for example, when defining Data Transfer Objects for an API. Records offer a concise way of defining classes representing immutable data:

public record Course(String title, String author, int minutes) {
}

With this record declaration, we can instantiate and use courses directly. A constructor taking every component of the record (e.g., title, author and minutes in this example) is provided by default. Components also get a corresponding private final field with a public accessor method. Last, a record automatically receives implementations for the equals, hashCode and toString methods. They use the component names and values to implement equality and provide a nicely formatted output when printing a record.

Here’s how you use the Course record we defined above:

var course = new Course("What's New in Java 16", "Sander Mak", 86);
var hours = course.minutes() / 60.0; // Accessor method
System.out.println(course); // Prints:
// Course[title=What's New in Java 16, author=Sander Mak, minutes=86]

Writing constructors, fields and accessor methods yourself is no longer necessary. By just declaring the components of a record, the compiler takes care of this automatically.

There are some limitations to records. For example, adding non-static fields to a record is prohibited. A record can’t extend another class or record, though a record can implement an interface. A record is always final, whether you declare it explicitly or not. All these restrictions ensure that whenever you have two records with exactly the same values for their components, they will always be regarded equal to each other:

var course1 = new Course("A course", "A. Uthor", 42);
var course2 = new Course("A course", "A. Uthor", 42);
course1 == course2; // false: we still have two distinct object instances
course1.equals(course2); // true: since all components are equal, the records are deemed equal as well

The second language change delivered with Java 16 is called ‘Pattern matching for instanceof'. Traditionally, you’d check and then cast an object to another type:

Object o = "Actually a String";
if (o instanceof String) {
  String s = (String) o;
  return s.length();
}

Notably, after the instanceof check, we need to introduce a variable of type String and cast the object o to String before we can use it as such. As of Java 16, you can use a type pattern on the right-hand side of instanceof instead:

if (o instanceof String s) {
  return s.length();
}

A type pattern defines both the dynamic type-check to be performed and a binding variable (s in this example). The value of o is assigned to s if and only if the type-check succeeds. If there had been an else-branch in this example, then the variable s would not be in scope, since the type-check is known to have failed when we’re in the else-branch!

In and of itself, pattern matching for instanceof seems like just a neat little feature. However, you can expect type patterns to appear in other places in later Java releases (e.g., in switches for Java 17) and other patterns to emerge in addition to type patterns. So instead, we’re seeing a preview of bigger things to come through this feature.

A last noteworthy change in Java 16 is the introduction of a new Java packing tool called jpackage. It allows you to package a Java application into a native installer package, for any of the major platforms: Windows, Mac and Linux.

Want to dive into more details on these Java 16 features? Check out my “What’s New in Java 16” course on Pluralsight.

What’s coming with Java 17

We can peek into the future already since Java is developed out in the open. Many Java Enhancement Proposals (JEPs) targeting Java 17 are publicly shared through the OpenJDK project. In addition to patterns in switches, we can expect sealed classes to land in Java 17 as a language feature.

Before sealed classes, any class (unless declared final) could be arbitrarily extended. This leads to an open-world assumption: You can’t assume subclasses encountered during compilation are exhaustive. At runtime, new classes extending the base class can appear at any time. With sealed classes, you can restrict inheritance of a class you define to only allowed subclasses:

public sealed class Vehicle permits Car, Boat, Plane

Now, no class besides the ones mentioned after the new permits keyword can extend the Vehicle class. As author of the Vehicle class, you now have more guarantees about the inheritance hierarchy. Interfaces can also be sealed. At a later stage, the combination records, patterns and sealed class can help the compiler check for exhaustiveness in switch constructions. That’s all beyond Java 17, however.

Java 17 is the next release to be designated as Long-Term Supported (LTS). At the moment, Java 11 is the most recent LTS release. Long-term support is offered commercially by JDK vendors (like Oracle). In practice, the whole Java community rallies around these LTS releases. You can expect many different JDK distributions to focus on delivering long-term supported JDK 11 and JDK 17 builds going forward.

If anything, this post should show you just how fast the Java platform is moving. With two releases every year, we get access to more advanced language features and improved JVM performance. In reality, many companies are still running on JDK 8. With the upcoming LTS release of Java 17, now’s certainly a good time to plan ahead and move with the times!



Related tags:

programming   java  
About the author

Sander Mak is a Fellow at Luminis in The Netherlands. At Luminis, he crafts modular and scalable software, most often on the JVM but with a touch of TypeScript where needed. He is the author of the O’Reilly book ‘Java 9 Modularity’ and an avid conference speaker. Sander loves sharing knowledge through his blog at Branch and Bound and also as a Pluralsight instructor.

10-day free trial

Sign Up Now