Java Sealed Classes

A sealed class explicitly restricts which classes or interfaces may directly extend or implement it. This lets Java model a controlled hierarchy whose permitted subtypes are known at the declaration site.

A sealed class is declared with the `sealed` modifier and a `permits` clause that names its allowed direct subclasses. A sealed interface works similarly for permitted direct implementors. The restriction applies to direct inheritance; permitted subclasses can themselves define further inheritance according to their own declarations.

Each permitted direct subtype must declare an inheritance status compatible with the sealed hierarchy: it can be `final`, `sealed`, or `non-sealed`. A `final` subtype stops further inheritance, a `sealed` subtype continues to restrict its own descendants, and a `non-sealed` subtype opens that branch to unrestricted extension.

The compiler checks that the declared permitted types actually participate in the hierarchy and satisfy the rules for sealed inheritance. In some cases, Java can infer permitted subclasses when the relevant types are declared in the same compilation unit, so an explicit `permits` clause is not always required.

Sealed hierarchies are useful when a domain has a deliberately limited set of variants, such as a fixed family of commands or result types. They also enable stronger compile-time reasoning, including exhaustive handling of subtype cases in supported language constructs.

Key points

Interview questions

What problem do sealed classes solve in Java?

Sealed classes let an API author restrict which types can directly extend or implement a type. This is useful when a domain has a known set of variants rather than an open-ended extension model. The compiler can then reason about that controlled hierarchy more precisely. It also communicates the intended inheritance structure directly in the type declaration.

What is the purpose of the `permits` clause?

The `permits` clause identifies the types allowed to directly extend a sealed class or implement a sealed interface. The compiler uses that information to enforce the inheritance boundary. Each permitted type must satisfy the rules of sealed inheritance. In situations where the compiler can infer the permitted types from declarations in the same compilation unit, an explicit `permits` clause can be omitted.

What must a permitted subclass declare?

A permitted direct subclass must be declared as `final`, `sealed`, or `non-sealed`. `final` ends that inheritance branch, while `sealed` creates another restricted level. `non-sealed` deliberately opens the branch so further subclasses are not constrained by the original sealed declaration. These modifiers make the intended continuation of the hierarchy explicit.

Can a sealed interface be implemented by a class hierarchy?

Yes. A sealed interface can specify permitted direct implementing classes, and those classes must follow the sealed-type rules. A permitted implementation can be `final`, `sealed`, or `non-sealed`, depending on whether its branch should close, remain restricted, or become open. The same concept also applies when permitted types are interfaces that extend a sealed interface.

Why are sealed types useful with exhaustive pattern matching?

A sealed hierarchy gives the compiler a bounded set of permitted direct subtypes. That information can help the compiler determine whether a pattern-based case analysis covers all relevant variants. This is particularly useful when representing a domain as a small, known family of types. Exhaustiveness still depends on the particular Java language feature and compiler rules being used.

When would you choose a sealed hierarchy instead of an ordinary interface?

I would use a sealed hierarchy when the abstraction intentionally has a bounded set of implementations and that boundary is part of the domain or API contract. An ordinary interface is better when third parties or future modules should be able to add implementations freely. Sealing provides stronger compile-time constraints and makes the intended type family explicit. The tradeoff is reduced extensibility, so the decision should follow the ownership and evolution requirements of the API.