Sealed Classes in Java

Sealed classes in Java - image of packing tape on top of a sealed box.

Introduction

Sealed classes were introduced as a preview feature in both Java 15 and 16, and then finalised in Java 17 as JEP 409.

Sealing allows classes and interfaces to define which classes can implement or extend them. This fine-grained inheritance control is useful for precise domain modelling and to improve the security of class libraries. Sealed classes also enable the compiler to check for pattern matching.

Motivation

We use inheritance for modelling hierarchies of related classes. From the first days of Java, it was assumed that code reuse was always a goal of inheritance. So every class was extendable by any number of subclasses. But code reuse is a benefit of inheritance, not necessarily the primary goal.

We want a superclass to be widely accessible because it is an important abstraction in our domain model. However, we might not want the superclass to be widely extensible, because we want to restrict its allowed subclasses. We will still get code reuse within this closed class hierarchy, but not outside it.

There were only a limited number of ways that we could control inheritance, e.g. if we only want a limited number of classes to extend from a superclass.

Let’s say we were developing a Employee class hierarchy. We want to extend the Employee class to Programmer, Secretary and Manager. However, we don’t want the Contractor class to extend from Employee, because contractors aren’t employees. They don’t get paid leave, promotions, covered parking, etc.

Java only has a limited number options for this: we can either make a class final, so it has no subclasses; or we can make the class package-private, so it can only have subclasses in the same package.

Sealed Classes

Sealed classes declaratively restrict the set of subclasses in a cleaner way than using access modifiers. They allow us to accurately model class hierarchies that should not be open to casual inheritance. Sealed classes allow a superclass to be widely accessible, but not widely extensible.

The sealed feature adds some new modifiers and clauses to Java. These are sealed, non-sealed, and permits.

A class is sealed by adding the sealed modifier to its declaration. The permits clause specifies the classes that are permitted to extend the sealed class. The permits clause comes after any extends and implements clauses.

For example, the following declaration of our Employee class specifies that only three specific subclasses are permitted:

package com.incusdata.employee.model;

public abstract sealed class Employee 
       permits Programmer, Secretary, Manager
       { /* controlled extension */ }

The classes specified by permits must be located near the superclass. These classes are modelled, coded and maintained together, so they shouldn’t be separated.

  • If we use modules, we must put the classes in the same module.

  • If we don’t use modules, then we must put the the sealed class and its direct subclasses in the same package.

  • However, if we don’t want to use modules, we can’t put the superclass in one interface package and the subclasses into a separate implementation package.

Sealed types and their direct subtypes can be generic.

Subclasses

A subclass of a sealed class must specify whether it is sealed, final, or open for extension. If it’s open for extension, it must be declared as non-sealed (yup, a hyphen in a keyword — horrors!).

package com.incusdata.employee.model;

public abstract sealed class Employee 
       permits Programmer, Secretary, Manager
       { /* controlled extension */ }

public final class Manager extends Employee 
        { /* cannot be extended. No more manager types - yay! */ }

public sealed class Programmer extends Employee 
       permits JavaProgrammer, PythonProgrammer 
       { /* controlled extension */ }

public non-sealed class Secretary extends Employee 
       { /* can be extended by any number of unknown classes */ }

When we don’t have many permitted subclasses and they are relatively small, we can declare them in the same source file as the sealed class. We can then leave out the permits clause in the sealed class. The Java compiler will infer the permitted subclasses from the other class declarations in the source file.

For example, if the Employee.java file contains the following code, then the sealed class Employee is inferred to have three permitted subclasses:

abstract sealed class Employee { ... 
    final class Programmer extends Employee { ... }
    final class Secretary  extends Employee { ... }
    final class Manager    extends Employee { ... }
}

Sealed Interfaces

An interface can be sealed in the same way as a class, with a fixed set of permitted direct subtypes. With a sealed interface, its direct subtypes can be both interfaces and classes.

The rules are the same for interfaces and classes. All direct subtypes must be listed in the permits clause, or be in the same source file. The subtypes must all be marked as final, sealed, or non-sealed.

We can implement a sealed interface with a record, which is implicitly final.

Reflection

Two methods have been added to java.lang.Class to support sealed classes.

  • The isSealed() method returns true for a sealed class.
  • The getPermittedSubclasses() method returns the permitted subclasses as an array of Class objects.

Summary

Sealed types are fairly straightforward. The key points to remember are:

  • A sealed type (class and/or interface) has a fixed set of direct subtypes.
  • The direct subtypes of a sealed type must be listed in a permits clause, or, if there is no permits clause, must be in the same source code file.
  • The direct subtypes of a sealed type must be final, sealed, or non-sealed.
  • Future pattern matching features can carry out exhaustiveness checking with sealed types.

For more technical information on sealed classes, see JEP 409.

Was this post useful? Please share your comments on the blog post, and as always, stay safe and keep learning!

Leave a Comment

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

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.