Java Sealed Classes

Java Sealed classes came out of preview state with the latest Java 17 release. Let’s explore what it does mean for Java developers.

Some Background

OOP languages are capable of modelling real-world scenarios very well. Java does a decent job already in this arena. Java emphasizes code reuse all the time with better modelling of inheritance. Enum classes allow us to model real-world scenarios of having a known set of values. You can assume those values as domain entities if you will. But there is a gap when representing a fixed set of kinds of entities. 

Let’s take a graphics parsing library as an example. The shape is a well-known abstraction of it. So it should be widely accessible, but if third parties can extend from it, the library developer has to take care of all possible error handling due to unintended usage and make the library extensible to others which may be not the design. Then the developer has to make the shape class package private, which will break all the possibilities of using it as an abstraction.

So for framework/libraries, developers had to use the final keyword and package-private constructors to limit the number of subclasses that particular superclass or interface may have. The intention is to restrict the extension by arbitrary subclasses. So the intended domain hierarchy is closed, yet inheritance works like a charm with code reuse. Earlier, this kind of design sacrificed abstraction most of the time for the sake of keeping a closed hierarchy.

Earlier Days

So let’s look at an example from JDK before the sealed feature.

package java.lang;

abstract class AbstractStringBuilder { ... }
public final class StringBuffer  extends AbstractStringBuilder { ... }
public final class StringBuilder extends AbstractStringBuilder { ... }

In this example, the class hierarchy doesn’t allow third parties to extend. Although code reuse is the goal in the above scenario, we can’t use superclass to switch over its subclasses or use it as an abstraction. If the superclass is made public, then anyone can extend from it. It will break the intention of having a closed hierarchy. The sealed feature resolves this issue.

Java Sealed classes

With the use of the sealed keyword and other supporting keywords, now you can make closed class hierarchies. So developers now can define class hierarchies where supper class is widely accessible while the extension is limited to a well-known set of subclasses.

What Java Sealed classes have to offer

Better Modeling

When language itself supports better domain modelling, it reveals the true intention of the programmer to the reader. We can use the sealed class feature with the supporting keywords to model the code in a more domain-driven, more readable manner with a clear indication for the developers about the domain of the hierarchy. Earlier, developers had to understand implicit mechanisms used to avoid the violation of the domain model.

Better Compiler Checks

The sealed class feature empowers the compiler to give better errors when working with the closed hierarchy source code. Read the usage section for the examples that compiler complains about unintended use of the sealed keyword and other supporting keywords. 

The sealed feature allows the developer to be more confident about runtime error handling since the compiler takes care of unintended classes popped out in the runtime.

Enhanced Pattern Matching

Pattern matching enables the Java compiler to deduce all the scenarios and give errors when all conditions are not covered. Following is a simple example of the power of pattern matching. 

subclass of a sealed class should be final, sealed or non-sealed
when switching over sealed subclasses (preview feature in java 17) it should cover all the possible cases

Pattern matching is itself a broader topic that is out of the scope of this article. Here is the Jetbrains blog post about enabling Java 17 preview features on the latest IntelliJ Idea version. They have done a great job explaining pattern matching and its usages, so I suggest adding it to your reading list.

Java Sealed Classes Usage

In single file

In a single file, you can have subclasses as many as you want without any issue. They all should be final, sealed or non-sealed. But there is a few semantics to follow.

subclass of sealed class should be final, sealed or non-sealed
subclass of a sealed class should be final, sealed or non-sealed
  • final 👉 good to go
  • non-sealed 👉 good to go
  • sealed 👉 it should have at least one subclass, either final, sealed or non-sealed

In module project

If you put a different shape subclass in the same package of the above file package, there will be a compiler error.

subclass in separate package of sealed class should be in permitted subclasses list
If subclass is in outside of sealed super class, it should be in permitted list of sealed superclass

To avoid the error, you have to use the permits keyword. The power of sealed functionality lies here. You can have a set of subclasses that permits the extension of the shape class. It avoids the extension of the shape class yet allows the use of abstraction outside of the package. In a single file, if there are no subclasses outside of it, the permits keyword is not required as compiler will assume the intention.

if any of subclass is outside of the package, all other subclasses should be in permitted list including class is in outside of the package

Permitted classes should also follow the rule of being final, sealed or non-sealed. Programmer can open the hierarchy by having a non-sealed class, but it remains useful since all subclass of it would be the subtype of the non-sealed class.

In non-module project

In a non-module project with all semantics mentioned earlier permitted class should be in the same module.

sealed semantics in rule form

  1. If a class or interface is `sealed` without permits keyword, then subclasses/subinterfaces should be in the same file.
  2. When class/interface is `sealed` and subclass/subinterface is in a different package of an unnamed module, it should be listed as permits.
  3. If there are multiple subclasses, all of them should be listed as permitted classes, although some of them are in the same file.
  4. Records are implicitly final, so without having a sealed/non-sealed modifier, it can use as a subclass similar to normal class.
  5. If a class is non-sealed it means it’s open for extension.

Points to note

  • Reflection API provides two methods to deal with sealed classes.
  1. Class<?>[] getPermittedSubclasses()
  2. boolean isSealed()
  • In the early days, developers weren’t able to use interfaces to have closed hierarchies. Interfaces methods are public by default. So it will allow anyone to extend from it. But with the sealed feature, now developers can have closed hierarchies yet exposing abstraction to the outside world.

Leave a Comment

Your email address will not be published. Required fields are marked *