In this article, we are going to go over the class/interface modifier sealed in Java which was introduced in JDK 15 (Preview) and officially became part of the Java language in JDK 17 LTS. Throughout this article, we will cover how to use the sealed
keyword, what problems it solves, and why it was added to the realm of Java keywords together with other supporting keywords, such as: permits
e non-sealed
. Finally, we will see through examples how you can use java sealed class.
1. Introduction
Prior to JDK 17, we had basically two options for controlling inheritance, the abstract
keyword for base classes that others must inherit from and the final
keyword for preventing inheritance, but we did not have anything in between. The sealed
keyword, which purpose is to restrict Inheritance, gives you, the developer, fine-grained control over inheritance and fixes this very problem.
There may be situations where you want to restrict inheritance to a specific set of subclasses and prevent just any other class, except those explicitly allowed by you, from extending it.
1.1 Before the Sealed Keyword
Before JDK 17 if you needed to limit inheritability, you would have to go for package-private (no explicit modifier) classes which could only be extended by other classes in the same package. However, it was not possible to create a widely accessible class while restricting inheritance.
2. Java Sealed Class Constraints
There are some constraints, imposed restrictions if you will, we need to have in mind while working with sealed
classes:
- Anonymous classes and local (inner) classes cannot be permitted subtypes of
sealed
classes. - The
sealed
class and its permitted subclasses must belong to the same module, and, if declared in an unnamed module, to the same package. - Every permitted subclass must directly extend the sealed class.
- Every permitted subclass must use a modifier to describe how it propagates the sealing initiated by its superclass:
final
: to prevent it from being extended by any means.sealed
: to allow only a subset of class to extend it → requires thepermits
keyword.non-sealed
: to allow just any class to extend it → the first hyphenated Java keyword.
- If the subclass is also declared as
sealed
, you will have to provide the classes that will extend it using the keywordpermits
. - If the
sealed
keyword is modifying aninterface
, it will only be possible for the sub-interfaces to be declared assealed
ornon-sealed
since having a “final interface“ would completely defeat the purpose of having one in the first place.
3. Declaring a Sealed Class in Java
To declare a sealed
class, simply add it before the keyword class
(either before or after any other class modifier, such as public
or abstract
) and, after the class name, add the keyword permits
followed by the class or classes that will mandatorily have to extend it.
public abstract sealed class Sealed permits NonSealedSubclass, SealedSubclass, FinalSubclass { private final String name; public Sealed(String name) { this.name = name; } public String getName() { return name; } }
We wanted to keep it simple, so we declared a class named Sealed with just a field, a constructor, and a getter, then we permitted three classes to extend Sealed, namely: NonSealedSubclass, SealedSubclass, and FinalSubclass (Each in a separate file).
NonSealedSubclass
public non-sealed class NonSealedSubclass extends Sealed { public NonSealedSubclass(String name) { super(name); } }
By declaring our class non-sealed
, we are allowing any class to extend it, and, should you want to prevent classes from different packages to extend it, just remove the public
keyword from its declaration.
SealedSubclass
public sealed class SealedSubclass extends Sealed permits FinalSealedSubclass { public SealedSubclass(String name) { super(name); } }
FinalSealedSubclass
public final class FinalSealedSubclass extends SealedSubclass { public FinalSealedSubclass(String name) { super(name); } }
Because we also declared this class as sealed
, we must add the permits
keyword, and we must provide the classes that will extend this one.
FinalSubclass
public final class FinalSubclass extends Sealed { public FinalSubclass(String name) { super(name); } }
By adding the final
keyword, this class will not be extended any further.
Keep in mind that trying to extend a sealed
class in a class that is NOT part of the permits
list will result in an error.
4. Nested Permitted Classes in Java
There may be situations where all you need is a couple of helper or nested classes, if that’s the case, you may leave the keyword permits
out and directly declare the class or classes within the sealed
class.
public sealed class NestedPermittedClass { static final class Local extends NestedPermittedClass { } public non-sealed class Global extends NestedPermittedClass { } private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } }
Notice that this time we did not need to use the permits
keyword and trying to extend NestedPermittedClass will result in an error. However, if you add the permits
keyword, you will have to add every single class name to the list, even the ones inside the sealed
class itself.
5. How to Declare Sealed Interfaces in Java
As discussed in Chapter 2, the only difference when it comes to extending sealed
classes and extending sealed
interfaces is that the latter CANNOT be final and, trying to do so will result in a compilation error: illegal combination of modifiers: interface and final.
Keep in mind we added fictional methods just for the sake of the example, that is, we won’t implement any of them.
public sealed interface Sealable permits NonSealable, SubSealable { void seal(); }
As you can our interface permits two other ones which mandatorily will have to extend Sealable.
non-sealed interface NonSealable extends Sealable { void unseal(); }
And now the second interface must also extend Sealable.
public sealed interface SubSealable extends Sealable permits Sealed { void sealFurther(); }
Now, because this is also a sealed interface, we have to compulsorily add every interface
and class
that must extend or implement this one.
import com.youlearncode.interfaces.SubSealable; public abstract sealed class Sealed implements SubSealable permits NonSealedSubclass, SealedSubclass, FinalSubclass { private final String name; public Sealed(String name) { this.name = name; } public String getName() { return name; } @Override public void seal() { System.out.println("Seal!"); } @Override public void sealFurther() { System.out.println("Seal Further!"); } }
One thing we must do is to implement those methods defined in the interfaces and avoid having to define them in every subclass, we have done it in the Sealed abstract class.
6. Nested Permitted Classes & Interfaces Under an Interface
In the same fashion, we can declare nested permitted interfaces and or classes in a sealed interface, and we can leave the permits
keyword out as well. However, if you plan on permitting more classes than those nested, you will have to list all of them, including nested ones.
public sealed interface NestedPermissible { void permit(); non-sealed interface Nestable extends NestedPermissible{ void sealNested(); } final class Nested implements NestedPermissible { @Override public void permit() { System.out.println("Permission granted!"); } } }
7. Record Classes as Permitted Subclasses
That’s the very same title on the Java SE 17 Language Update Official Page on Sealed Classes and its statement reads:
You can name a record class in the
Sealed Classes (oracle.com)permits
clause of a sealed class or interface.
In practice, however, whenever you declare a record
class (click here to check our article on record classes), it implicitly and automatically extends java.lang.Record
class and we all know Java does not allow for multiple inheritance.
In other words, we CANNOT add record classes to the list of permitted classes in a sealed class. All we can do is add it to a list of permitted classes and interfaces in an interface.
Check what the JEP on Record classes states:
Restrictions on records:
Java Enhance Proposal 359
Records cannot extend any other class, and cannot declare instance fields other than the private final fields which correspond to components of the state description.
First, let’s add our soon-to-be-created record
class to the list of permitted subclasses/interfaces in our Sealable interface.
import com.youlearncode.RecordClass; public sealed interface Sealable permits NonSealable, SubSealable, RecordClass { void seal(); }
Finally, here is our record class which implements Sealable.
import com.youlearncode.interfaces.Sealable; public record Record(String name) implements Sealable { @Override public void seal() { System.out.println("Record Seal!"); } }
8. Conclusion
By now you should be able to fully understand how sealed classes work, how to restrict and enforce inheritance, and most importantly, how to have full control of your model by determining how entities relate to one another.
9. Sources
[1]: Sealed Classes (oracle.com)
[2]: Record Classes (oracle.com)