New Features From Java 12 to 17

Three years after Java 11 (the last long-term support version so far), Java 17 LTS will be released in September 2021. Time to take a quick tour through the new features that developers can enjoy after upgrading from 11 to 17. Note that many more improvements have been made under the hood - this article focuses on those features that can be directly used by most developers:

Switch Expressions

The switch can now return a value, just like an expression:

Java
 
// assign the group of the given planet to a variable
String group = switch (planet) {
  case MERCURY, VENUS, EARTH, MARS -> "inner planet";
  case JUPITER, SATURN, URANUS, NEPTUNE -> "outer planet";
};

If the right-hand side of a single case requires more code, it can be written inside a block, and the value returned using yield:

Java
 
// print the group of the given planet, and some more info,
// and assign the group of the given planet to a variable
String group = switch (planet) {
  case EARTH, MARS -> {
    System.out.println("inner planet");
    System.out.println("made up mostly of rock");
    yield "inner";
  }
  case JUPITER, SATURN -> {
    System.out.println("outer planet");
    System.out.println("ball of gas");
    yield "outer";
  }
};

However, switching with the new arrow labels does not require returning a value, just like a void expression:

Java
 
// print the group of the given planet
// without returning anything
switch (planet) {
  case EARTH, MARS -> System.out.println("inner planet");
  case JUPITER, SATURN -> System.out.println("outer planet");
}

Compared to a traditional switch, the new switch expression

Moreover, the compiler guarantees switch exhaustiveness in that exactly one of the cases gets executed, meaning that either

Text Blocks

Text blocks allow writing multi-line strings that contain double quotes, without having to use \n or \" escape sequences:

Java
 
String block = """
  Multi-line text
   with indentation
    and "double quotes"!
  """;

A text block is opened by three double quotes """ followed by a line break, and closed by three double-quotes.

The Java compiler applies a smart algorithm to strip leading white space from the resulting string such that

In the above example, the resulting string looks as follows, where each . marks a white space:

Java
 
Multi-line.text
.with.indentation
..and."double.quotes"!

Imagine a vertical bar that spans the text block’s height, moving from left to right and deleting white spaces until it touches the first non-whitespace character. The closing text block delimiter also counts, so moving it two positions to the left

Java
 
String block = """
  Multi-line text
   with indentation
    and "double quotes"!
""";

Results in the following string:

Java
 
..Multi-line.text
...with.indentation
....and."double.quotes"!

In addition, trailing white space is removed from every line, which can be prevented by using the new escape sequence \s.

Line breaks inside text blocks can be escaped:

Java
 
String block = """
    No \
    line \
    breaks \
    at \
    all \
    please\
    """;

Results in the following string, without any line breaks:

No.line.breaks.at.all.please

Alternatively, the final line break can also be removed by appending the closing delimiter directly to the string’s end:

Java
 
String block = """
    No final line break
    at the end of this string, please""";

Inserting variables into a text block can be done as usual with the static method String::format, or with the new instance method String::formatted, which is a little shorter to write:

Java
 
String block = """
    %s marks the spot.
    """.formatted("X");

Packaging Tool

Suppose you have a JAR file demo.jar in a lib directory, along with additional dependency JARs. The following command

jpackage --name demo --input lib --main-jar demo.jar --main-class demo.Main

Packages up this demo application into a native format corresponding to your current platform:

The resulting package also contains those parts of the JDK that are required to run the application, along with a native launcher. This means that users can install, run, and uninstall the application in a platform-specific, standard way, without having to explicitly install Java beforehand.

Cross-compilation is not supported: If you need a package for Windows users, you must create it with jpackage on a Windows machine.

Package creation can be customized with many more options, which are documented on the jpackage man page.

Pattern Matching for Instanceof

Pattern matching for instanceof eliminates boilerplate code for performing casts after type comparisons:

Java
 
Object o = "string disguised as object";
if (o instanceof String s) {
  System.out.println(s.toUpperCase());
}

In the example above, the scope of the new variable s is intuitively limited to the if branch. To be precise, the variable is in scope where the pattern is guaranteed to have matched, which also makes the following code valid:

Java
 
if (o instanceof String s && !s.isEmpty()) {
  System.out.println(s.toUpperCase());
}

And also vice versa:

Java
 
if (!(o instanceof String s)) {
  throw new RuntimeException("expecting string");
}
// s is in scope here!
System.out.println(s.toUpperCase());

Records

Records reduce boilerplate code for classes that are simple data carriers:

record Point(int x, int y) { }

This one-liner results in a record class that automatically defines

Java
 
// canonical constructor
Point p = new Point(1, 2);
    
// getters - without "get" prefix
p.x();
p.y();
    
// equals / hashCode / toString
p.equals(new Point(1, 2)); // true
p.hashCode();              // depends on values of x and y
p.toString();              // Point[x=1, y=2]

Some of the most important restrictions of record classes are that they

However, it is possible to

Java
 
record Point(int x, int y) {

  // explicit canonical constructor
  Point {

    // custom validations
    if (x < 0 || y < 0) 
      throw new IllegalArgumentException("no negative points allowed");

    // custom adjustments (usually counter-intuitive)
    x += 1000;
    y += 1000;

    // assignment to fields happens automatically at the end

  }
  
  // explicit accessor
  public int x() {
    // custom code here...
    return this.x;
  }
  
}

Besides, it is possible to define a local record inside a method:

Java
 
public void withLocalRecord() {
  record Point(int x, int y) { };
  Point p = new Point(1, 2);
}

Sealed Classes

A sealed class explicitly lists the permitted direct subclasses. Other classes must not extend from this class:

Java
 
public sealed class Parent
  permits ChildA, ChildB, ChildC { ... }

Likewise, a sealed interface explicitly lists the permitted direct subinterfaces and implementing classes:

Java
 
sealed interface Parent
  permits ChildA, ChildB, ChildC { ... }

The classes or interfaces in the permits the list must be located in the same package (or in the same module if the parent is in a named module).

The permits the list can be omitted if the subclasses (or interfaces) are located inside the same file:

Java
 
public sealed class Parent {
  final class Child1 extends Parent {}
  final class Child2 extends Parent {}
  final class Child3 extends Parent {}
}

Every subclass or interface in the permits the list must use exactly one of the following modifiers:

 

 

 

 

Top