Java 17 (LTS) New Language Features

Giorgi Tsiklauri
5 min readAug 31, 2021

On the 14th of September, 2021, we’ve got General Availability of the Java 17 (JSR 392) Reference Implementation - OpenJDK 17, the next LTS version after Java 11 that was released back in September, 2018.

In this post, I will try to cover, in depth, new features of the Java 17 programming language.

So, what’s new in Java SE 17?

  • Sealed Classes (JEP 409)
  • Pattern Matching for switch (JEP 406)

Let’s now learn everything about these new features.

Sealed Classes (JEP 409)

This is probably one of the most notable and important enhancement in Java 17.

Sealed Classes were proposed by JEP 360 and delivered in JDK 15 as a preview feature. They were proposed again, with some refinements, and delivered in JDK 16, still, as a preview feature. Now in JDK 17, Sealed Classes are being finalized with no changes from JDK 16.

What is sealed class?

Sealing a class or interface means defining a clear contract about which other classes can extend (in case you seal a class) or implement (in case you seal an interface) the sealed type.

By the way, I may refer to sealed classes or interfaces, interchangeably, as sealed types, because you can seal a class (both, normal and abstract), and you can seal an interface.. and besides, we all know, that classes and interfaces are both classes (we know, right?)

You define sealed class as follows:

public sealed class Shape permits Circle, Square {    //this class only permits Circle and Square to derive from it}class Circle extends Shape { }class Square extends Shape { }

How does this translate into plain English?

class Shape only permits classes Circle and Square to extend it, and no other classes can extend Shape.

Important constraints and notes:

  • permits is a new reserved keyword (introduced in JDK 15), and you use it to enlist classes you want to permit to be the subtypes of your sealed class (Circle and Square are only allowed types to extend Shape in above example);
  • sealed type must have one or more subtypes, i.e. (1) it must declare at least one type that it permits to be the subtype; and (2) whatever sealed type permits, (remember this as a rule) must be doing whatever it/they is/are permitted to do, otherwise, it’s a compile-time error.
    Huh.. sounds like an obligation.. not a permission.
    Let me shed more light here: whenever you seal a type by permitting only specific type(s) to extend (if you seal a class) or implement (if you seal an interface) it, whatever you permit to be a subtype(s) of your sealed type, must be declaring to be the direct(!) subtype(s) of your sealed type (they must be directly extending your sealed class, or directly implementing your sealed interface);
  • Any extends or implements clauses your sealed class might declare, must go before permits clause;
  • sealed types and its permitted types must be in the same module or in the same package (you need to understand JPMS here.. if you don’t, it is probably better not to deviate now, it’s going to take quite a big time);
  • You may permit record class - that’s completely fine (you will see below — why)
  • non-access modifier sealed can precede or follow class keyword;

I’ll reveal one more crucial note, in a moment. Let’s first try to compile the code above and run:
bin/javac Shape --enable-preview -source 16 .. and voilà! it produces:

Shape.java:4: error: sealed, non-sealed or final modifiers expected
class Circle extends Shape { }
^
Shape.java:5: error: sealed, non-sealed or final modifiers expected
class Square extends Shape { }

Yikes!.. what..?

Remember by heart:

sealed type can only be subtyped (sub-classed or implemented) by either sealed, final or non-sealed types.

So, if you seal the type Shape, and declare it to be permitting Square, then Square must fulfill one of these three points in XOR fashion:

  1. Square must be final, which means you won’t be able to extend it (now you know why records can be permitted);
  2. Square must be also sealed, which means, it has to permit some types to be extenders/implementers, and this, in turn, means, that same rules will apply to those grandchild types;
  3. Square must be non-sealed, that’s another new keyword in Java 17 (available as a preview feature since Java 15).

That’s pretty much all you need to know about sealed classes.

You may feel a bit cluttered.. a bit overwhelmed (in either — positive or negative semantics).. or a bit ironic, questioning:

Why, on earth, we might need this, at all..

We can refer to official goals of the JEP and see, that sealed classes:

  • Allow the author of a class or interface to control which code is responsible for implementing it.
  • Provide a more declarative way than access modifiers to restrict the use of a superclass.
  • Support future directions in pattern matching by providing a foundation for the exhaustive analysis of patterns.

Basically, it’s yet another level of access granularity for Java types. It’s a fine-grained way to control the access-level in inheritance hierarchy of our classes.

Pattern Matching for switch (Preview Feature) (JEP 406)

In 2020, Java 14 introduced Pattern Matching for instanceof (JEP 305) preview feature and we’ve covered it thoroughly.

JEP 305 was then superseded by JEP 375, a Second Preview, which had been proposed to re-preview the feature, again, in JDK 15, with no changes relative to the JEP 305, in Java 14, and the reason was simple — to observe this feature deeper, and try to get and gather additional feedback on this feature.

Finally, JEP 394 finalised and standardised Pattern Matching for instanceof feature, with a few tiny updates, and it was delivered in Java 16.

Java 17 introduces newer approach in using pattern matching, and proposes to use it with switch statement, as a preview feature, for now.

Patterns can now:

  • appear in case labels;
  • come in a newer forms: guarded patterns and parenthesized patterns;

Problem with switch is that it is very limited when it comes to types. You can only switch on values of a very few types — numeric, enum, and String types, namely. Also, you can only test for exact equality against constants.

We may want to use patterns to test one, same variable, against number of possibilities, taking a specific action on each, but since the existing switch does not support that, we end up with a chain of if…else.

Having long if-else blocks are — again, boilerplate and repetitive.

With switch, however, we can now use patterns, and do:

String myFormatter(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}

That is Pattern Matching for switch.

--

--

Giorgi Tsiklauri

Software engineer, architect, lecturer; long while w/ computers and music; interested in software design, computer networks, composition, security and privacy.