Sealed Classes
Sealed Classes
Java's type system has always let any class extend any other class — unless you mark it final. But final is an all-or-nothing switch: either nobody can extend you, or everyone can. Sealed classes, introduced as a standard feature in Java 17, give you a middle ground: you decide precisely which classes are allowed to extend (or implement) yours. The compiler enforces that list, and nothing else can join the hierarchy.
The Problem Sealed Classes Solve
Imagine you are modeling the result of a payment operation. You want exactly three outcomes: Success, Failure, and Pending. With plain inheritance, any external code could add a fourth subclass, silently breaking your exhaustive switch logic. Sealed classes make the hierarchy closed and known at compile time, so the compiler can verify that you have handled every case.
Syntax: sealed and permits
Declare a sealed class with the sealed modifier and list every permitted subclass in a permits clause:
Each permitted subclass must then declare one of three modifiers:
final— the subclass itself cannot be extended further.sealed— the subclass is also sealed and must provide its ownpermitslist (allows a deeper restricted hierarchy).non-sealed— the subclass reopens the hierarchy; anyone can extend it freely.
Here is the full example with all three outcomes as final records (records are a natural fit — more on that in the next lesson):
Using the Sealed Hierarchy in a switch
The real payoff comes with pattern matching for switch (covered in detail in Lesson 9). Because the compiler knows every permitted subtype, it can check that your switch is exhaustive — no default branch needed:
If you later add a fourth permitted class, the compiler immediately flags every switch that does not handle it. This is algebraic data type safety in Java.
Nesting: sealed Subclasses
A permitted subclass may itself be sealed, letting you build a two-level restricted tree:
The full set of concrete types the compiler sees is: Circle, Triangle, Rectangle. A switch on Shape must handle all three.
non-sealed: Reopening the Hierarchy
Sometimes you want to restrict most of a hierarchy but allow one branch to be freely extended by third parties. Mark that branch non-sealed:
non-sealed removes exhaustiveness guarantees for that branch. The compiler can no longer verify that a switch handles every possible CustomNotification subtype. Only use it when open extension is intentional.
File and Package Rules
There is one placement rule to know: each permitted subclass must be in the same package (or in the same compilation unit) as the sealed parent. If they are in separate files, they all go in the same package — you cannot permit a class from another package. This is intentional: sealed hierarchies are meant to represent a closed, co-owned set of types.
Summary
Sealed classes let you declare a fixed, compiler-enforced set of subtypes. Use sealed … permits … on the parent, then mark each permitted child final, sealed, or non-sealed. The hierarchy is documented, versioned, and exhaustively checkable — especially valuable when combined with pattern-matching switch. In the next lesson, you will see how sealed interfaces pair with records to produce concise, safe algebraic data types.