Learn Java

Sealed Classes and Interfaces in Java

This topic is dedicated to a feature that has been first introduced in Java 15 as a preview feature and was finally added to the language in Java 17 version. We will talk about sealed classes and interfaces. This feature allows us to decide which classes or interfaces can derive from the base one.

The main idea

Inheritance is one of the pillars of OOP programming languages. It enables a class or interface to derive from another class or interface. Before this update, restrictions were set by the access modifier only. Any class or interface that had access to the base one could inherit from it if it wasn't set as final (in the case of classes). Thanks to the update, now we can determine which specific classes or interfaces can do that. It is set using the permits keyword.

It is not always mandatory to explicitly mention permitted classes or interfaces. If all derived classes and interfaces are in the same source file as the sealed class or interface, you can skip that step. But if you do, you must declare all classes and interfaces from the permitted list, otherwise, you will face a compilation issue.

Let's see how it works with classes and interfaces separately. Here is an example with a sealed class:

public sealed class Shape
        permits Triangle, Square { }


final class Triangle extends Shape { }

final class Square extends Shape { }

We mark the class as sealed and mention the class/classes that are allowed to extend it.

In the case of named modules, if the derived classes and interfaces are located in different packages, you should explicitly show the package path in a permits clause.

The same approach works with interfaces:

sealed interface ShapeInterface
        permits Triangle, TriangleInterface { }

sealed class Shape
        permits Triangle { }


final class Triangle extends Shape implements ShapeInterface { }

non-sealed interface TriangleInterface extends ShapeInterface { }

As you can see, this mechanism works not only when a class implements an interface, but also when an interface extends the sealed interface.

If a class or interface is marked as sealed, it should have at least one subtype. Otherwise, the application won't compile. We will explore the non-sealed modifier in the next section.

Restrictions

This feature supports three access modifiers for permitted classes or interfaces. All of them should be marked with only one of those modifiers.

  • Final — which can be used with classes only
  • Sealed — a permitted class or interface marked as sealed should also have its subtype
  • Non-sealed — a class with this modifier can be extended by another class. The same can be applied to interfaces, which can be a base for other classes and interfaces. This modifier's purpose is to open special points for extension in the sealed hierarchy while others are restricted

These modifiers can't be combined: final sealedfinal non-sealed, or sealed non-sealed will cause a compilation issue.
Note that if you extend the permitted class, you will still remain in the class hierarchy of the highest base class. Type checking against it will return true.

public class SealedDemo {
    public static void main(String[] args) {
        RightTriangle triangle = new RightTriangle();
        boolean isInstance = triangle instanceof Shape;

        System.out.println(isInstance); // true
    }
}

sealed class Shape permits Triangle { }

sealed class Triangle extends Shape permits RightTriangle { }

non-sealed class RightTriangle extends Triangle { }

The same logic works for interfaces as well.

Sealed and records

Records are also fine to be used together with the sealed class modifier. However, they can't inherit from a class since all records already extend java.lang.Record implicitly and Java doesn't support multiple inheritance. So, records can implement a sealed interface only.

sealed interface ShapeInterface permits Circle { }

record Circle (double radius) implements ShapeInterface { }

Since record classes are final by default, you can't use sealed or non-sealed modifiers here.

Exhaustive compile-time checking

The sealed feature has also brought us exhaustive compile-time checking. Consider the following code:

public class SealedDemo {
    public static void main(String[] args) {
        Triangle triangle = new Triangle();
        System.out.println(printType(triangle));
    }

    public static String printType(Shape shape) {
        if (shape instanceof Triangle) return "The shape is a triangle";
        else if (shape instanceof Square) return "The shape is a square";
        else throw new IncompatibleClassChangeError();
    }
}

sealed class Shape
        permits Triangle, Square { }

final class Square extends Shape { }
final class Triangle extends Shape { }

The compiler doesn't know how to check whether all subtypes are included in the if block, but the new pattern matching feature using switch is smarter and doesn't require the default case label if checking against all subtypes is performed. Moreover, it can warn if you didn't cover any subtype type checking.

Sealed and non-sealed classes may be abstract and permit abstract subclasses.

public abstract sealed class Shape permits Triangle, Square {
    public static void main(String[] args) {
        Triangle triangle = new Triangle();

        System.out.println(printShapeType(triangle)); // The shape is a triangle
    }

    public static String printShapeType(Shape shape) {
        return switch (shape) {
            case Triangle t -> "The shape is a triangle";
            case Square s -> "The shape is a square";
        };
    }
}

final class Triangle extends Shape { }

final class Square extends Shape { }

The code above will compile and print The shape is a triangle without any compile-time error messages.

Conclusion

This topic introduced you to a new Java feature that helps us design a more precise code hierarchy. Thanks to it you will have more control over your code and increase its security level. It might seem unusual at first to build applications using this powerful feature, but over time and with some due practice, you are sure to learn how to use it to your advantage.

Create a free account to access the full topic

“It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.”
Andrei Maftei
Hyperskill Graduate