Skip to content

Sealed classes and interfaces in JDK 17

Sealed classes and interfaces Java

1. Overview

In this article, we will learn the new feature Sealed classes and interfaces of Java.

Java introduces the Sealed classes and interfaces as a preview feature in JDK 15 (JEP 360) and JDK 16 (JEP 397). Now, finalized the feature in JDK 17 (JEP 409), with no changes from JDK 16.

Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them. It is more of a declarative way to restrict the use of a superclass rather than using access modifiers.

If you want to understand the purpose of Sealed classes and interfaces, proceed with below sections. Otherwise, navigate to this section to get started with sealed classes and interfaces implementation.

2. Code reusability using Java inheritance

Code reusability is one of the fundamental purposes of inheritance.

When you create a new class and there is already a class available with some of the code you need, you would extend the existing class in your new class. By doing so, you can reuse the existing fields and methods of the superclass without having to rewrite them again.

In Java, a class can be final so no other classes can subclass it. If a class is not final, then it is open to all other classes to support code reusability. Doing so would raise data modeling concerns.

3. Limitations of traditional inheritance

A data model is a logical organization of real-world entities (classes) and standardizes how they relate to one another. Assume you want to model the various possibilities that exist for a real-world scenario by defining the required classes and determining how these classes should relate to each other.

Let’s see an example to understand this concept.

Consider our modeling requirement is to create a Direction enum class and have only a fixed set of values. Let’s take the following Direction enum class that has a fixed set of constants (NORTH, SOUTH, EAST, WEST). Here, we defined the Direction enum class and determined what values it can hold.

Also, if you are going to cover all these constants in your switch expression, then you can ignore a default clause:

enum Direction { NORTH, SOUTH, EAST, WEST }

Direction direction = Direction.NORTH;
switch (direction) {
  case NORTH: { System.out.println("North"); }
  case SOUTH: { System.out.println("SOUTH"); }
  case EAST: { System.out.println("EAST"); }
  case WEST: { System.out.println("WEST"); }
}

Let’s take a similar example in inheritance.

The below NumberSystem class is open to all classes, so any subclass can extend it. What if you want to restrict this NumberSystem to a fixed set of subclasses (Binary, Decimal, Octal, and HexaDecimal)?. It means you don’t want any other arbitrary class to extend this NumberSystem class.

class NumberSystem { ... }

final class Binary extends NumberSystem { ... }
final class Decimal extends NumberSystem { ... }
final class Octal extends NumberSystem { ... }
final class HexaDecimal extends NumberSystem { ... }

You can only follow any of the below approaches to restrict the class from being implemented or extended by other classes.

  1. Make the class final, so it has zero subclasses
  2. Make the class or its constructor package-private, so it can only have subclasses in the same package

The first approach makes no sense here. The second approach restricts the access to the same package, but doesn’t satisfy all of our data modeling requirements i.e., to restrict the NumberSystem class to a known set of subclasses (Binary, Octal, Decimal, HexaDecimal).

Using sealed class, you can achieve it by controlling the subclasses that can extend it and prevent any other arbitrary class from doing so.

4. Benefits of Java sealed classes and interface

The sealed classes and interfaces would be widely accessible but not widely extensible (restricted to a fixed set of subclasses).

Code reusability is still possible but within a closed class hierarchy.

Supports data modeling

5. Sealed classes and interfaces

5.1. Sealed classes

The syntax to define the Java sealed class is:

sealed class <sealed class name>
    permits <subclass1>, <subclass2>, <subclass3> {
}

To seal a superclass, add the sealed modifier to its declaration as above. After any extends and implements clauses, then add the permits clause. This clause specifies the subclasses that can extend the sealed class.

Let’s take an example with permits clause.

If your subclasses are in different files in the same module or package, then use permits clause to define the subclasses that may extend the sealed class:

public sealed class NumberSystem
    permits Binary, Decimal, Octal, HexaDecimal {
}

If all of your subclasses are in the same file as the superclass (sealed class), then you can omit permits clause:

package com.tedblob.numbersystem;

public sealed class NumberSystem


{ }

final class Binary extends NumberSystem { .. }
final class Octal extends NumberSystem { .. }
final class HexaDecimal extends NumberSystem { .. }
sealed class Decimal extends NumberSystem { .. }

final class NonRecurringDecimal extends Decimal {..}
final class RecurringDecimal extends Decimal {..}

In the above code, we omitted the permits clause from the sealed class NumberSystem as all the subclasses that extend the NumberSystem are defined in the same file.

5.1.2. Limitations of sealed classes

The permitted subclasses have the following limitations:

  • Must be accessible by the sealed class at compile time. For example, the compiler must be able to access all the subclasses Binary, Octal, HexaDecimal and Decimal while compiling the NumberSystem. Since Decimal is a sealed class, the compiler also needs access to its NonRecurringDecimal and RecurringDecimal subclasses.
  • Permitted subclasses must extend the sealed class directly.
  • Allowed modifiers:
    • final
    • sealed
    • non-sealed
  • They must be in the same module or the same package as the sealed class

5.2. Non-sealed classes

package com.tedblob.numbersystem;

public sealed class NumberSystem


{ }

final class Binary extends NumberSystem { .. }
final class Octal extends NumberSystem { .. }
final class HexaDecimal extends NumberSystem { .. }
non-sealed class Decimal extends NumberSystem { .. }

final class NonRecurringDecimal extends Decimal {..}
final class RecurringDecimal extends Decimal {..}

In the above example, you have permitted only the specified classes (Binary, Octal, HexaDecimal and Decimal) to extend NumberSystem.

Consider you want to allow unknown classes to extend the Decimal subclass. Though, the NumberSystem root level hierarchy is closed to set of known classes, you can allow the sub hierarchies to be open by using the non-sealed keyword.

The sealed and non-sealed combination allows you to restrict parts of your hierarchy but not all.

In the below diagram, we restricted the root hierarchy of the sealed class NumberSystem to a known set of subclasses. However, the non-sealed Decimal class allows any unknown subclass such as RecurringDecimal to extend it.

So part of the inheritance hierarchy (Decimal) is open to all classes.

Sealed and non-sealed combination
Sealed and non-sealed combination

5.3. Sealed interface

There are scenarios where you don’t want the default methods of the interface to be accessible by unknown classes or interfaces. A sealed interface can prevent unknown classes or interfaces from accessing it.

The syntax of the sealed interface is:

sealed interface <sealed interface name>
    permits <permitted classes or interfaces> {
}

Like the Sealed classes, follow the below steps to declare an sealed interface:

  • Add the sealed modifier before the interface keyword in the interface declaration
  • After any extends clause, add the permits clause which specifies the classes that can implement and the interfaces that can extend the sealed the interface

For example, the below Expr is a sealed interface that permits MathExpr and NotANumber classes to implement it. This defaultMethod will not be accessible by other unknown classes.

sealed interface Expr permits MathExpr, NotANumber { 
    default void defaultMethod() {

    }
}

sealed class MathExpr() implements Expr { .. }
final class NotANumber implements Expr { .. }

5.4. Difference between a final and a non-sealed class

A final class

A final class has zero subclasses, meaning no other class can extend it.

A non-sealed class

Any class can extend the non-sealed class.

When you mark a class as sealed, only the permitted subclasses can extend it and can have only these modifiers finalsealed or non-sealed:

  • Any unknown subclass can extend the non-sealed class and is open. It just breaks the sealed class effect and doesn’t pass the restriction down the hierarchy.

6. Conclusion

To sum up, we have learned the sealed classes and interfaces of Java.

Leave a Reply

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