skip to content

Sealed Classes

Sealed types let a class or interface name exactly which types may extend it, closing a hierarchy for the compiler. Combined with pattern matching, this is Java's answer to algebraic data types and a popular modern-Java question.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

8

What is a sealed class or interface in Java, and what problem does it solve?

level: juniorimportance: must knowfreq 72%

answer

  1. Closed set of subtypes, not open and not final
  2. sealed + permits clause
  3. Enables exhaustive switch with no default
  4. Java 17 permanent feature
  5. Middle ground between final and open

basics

~10 s

A sealed class or interface restricts which other classes can extend or implement it. You list the allowed subtypes with the permits clause, so no unexpected class can join the hierarchy.

solid answer

~40 s

A sealed type (class or interface, since Java 17) lets the author of a type control exactly which classes may extend or implement it, using the sealed modifier plus an optional permits clause naming the allowed subtypes. Before sealed types you had two blunt choices: final (no subtypes at all) or open (anyone, anywhere can extend). Sealed gives a middle ground: a known, closed set of subtypes. This makes a hierarchy a true algebraic 'sum type' - the compiler knows the complete list of alternatives. That enables exhaustive switch over the permitted subtypes (no default branch needed), safer modelling of domain states, and stronger encapsulation because library authors prevent outsiders from injecting rogue subclasses.

go deeper

for a junior

Can state that sealed restricts which classes may extend/implement a type via permits, and that it sits between final and fully open.

for a middle

Explains the exhaustive-switch benefit, knows permits can be inferred for same-file subtypes, and gives a real modelling example.

for a senior

Frames sealed types as algebraic sum types, connects them to pattern matching and API encapsulation, and discusses when to choose sealed over an enum or open hierarchy.

for a principal

Reasons about sealed types in API/module design: stability of the permitted set as a public contract, evolution risk of adding a subtype, and trade-offs versus visitor/enum patterns across module boundaries.

## The problem In Java, a normal (non-final) class or interface is **open for extension**: any class, anywhere, can `extend` or `implement` it. Sometimes that is good (frameworks rely on it). But often a type author wants a **fixed, known set of subtypes** and nothing else. Historically you only had two extremes: - `final class Foo` - **nobody** can subclass it. Too restrictive if you legitimately want a few subtypes. - `class Foo` - **anybody** can subclass it. Too permissive; you lose control and you can never know the full list. A **sealed type** (added as a permanent feature in **Java 17**, previewed in 16) fills the gap: it stays extensible, but **only by an explicitly listed set of types**. ## Syntax ```java public sealed interface Shape permits Circle, Square, Rectangle {} ``` - `sealed` is a modifier on the class/interface. - `permits X, Y, Z` lists the **only** types allowed to directly extend/implement it. The `permits` clause can be **omitted** if all permitted subtypes are in the **same source file** - the compiler infers them. ## Why it matters 1. **Closed set = algebraic sum type.** A sealed hierarchy says 'a Shape is *exactly one of* Circle, Square, or Rectangle.' The compiler now knows the complete universe of alternatives. 2. **Exhaustive `switch`.** Because the set is closed, a `switch` over a sealed type that covers every permitted subtype is **exhaustive** - the compiler accepts it with **no `default` branch**, and will give a compile error if you later add a subtype and forget to handle it. This is the single biggest practical win, especially combined with **pattern matching** for `switch` and record patterns. 3. **Encapsulation / API control.** A library author can publish a type that the outside world can *use* but **cannot extend with rogue implementations** - the permitted list is fixed at compile time. 4. **Better domain modelling.** States like `Loading | Loaded | Failed`, expression trees, results, etc. map cleanly to sealed hierarchies. ## Key terms defined - **Subtype / subclass:** a type that `extends` (for classes) or `implements` (for interfaces) another. - **Permitted subtype:** a type named in the `permits` clause (or inferred); the only ones allowed. - **Exhaustive switch:** a `switch` whose branches cover every possible value, so no `default` is needed. - **Sum type / algebraic data type:** a type defined as 'one of a fixed set of alternatives'. ## Contrast at a glance | Modifier | Who can subtype | |---|---| | `final` | nobody | | `sealed` | only the permitted list | | (none) | anybody | | `non-sealed` | reopens the branch to anybody again | Sealed types are a **modelling and safety** feature first; they do not change runtime performance meaningfully - the benefit is at compile time.

  • Can the permits clause be omitted?
    Yes - if every permitted subtype lives in the same source file (or same compilation unit), the compiler infers the permitted list, so you can write just sealed without permits.
  • In which Java version did sealed classes become a permanent (non-preview) feature?
    Java 17 (previewed in Java 15 and 16).

A sealed type is like a guest list at a private event: it's not locked (final) and not open to the public (no modifier) - only the named invitees may enter.

saying these in an interview costs you the question

  • Saying sealed means 'cannot be subclassed at all' - that is final; sealed allows a controlled set
  • Claiming sealed gives a runtime performance boost - the benefit is compile-time safety and modelling
  • Thinking permits is always mandatory - it can be inferred when subtypes are in the same file

context

open as a page

Every direct subtype of a sealed class must itself declare one of three modifiers - which three, and what does each mean?

level: middleimportance: must knowfreq 68%

basics

~10 s

Each permitted subclass must be final (no further subclasses), sealed (continues the closed hierarchy with its own permits list), or non-sealed (reopens that branch so anyone can extend it).

open as a page

What does it mean for a switch over a sealed type to be exhaustive, and how does a sealed hierarchy let the compiler verify exhaustiveness without a default arm?

level: middleimportance: must knowfreq 55%

basics

~20 s

A sealed type lists exactly which subtypes can exist. If your switch has a case for every permitted subtype, the compiler knows nothing else is possible and accepts it as complete (exhaustive) without a default branch.

open as a page

What are the location/accessibility constraints on a sealed type and its permitted subtypes (same module/package, etc.), and why do they exist?

level: seniorimportance: should knowfreq 52%

basics

~20 s

A sealed type and all its permitted subtypes must live together: in a named module, in the same module; otherwise in the same package. Each permitted subtype must directly extend the sealed type and be accessible to it at compile time.

open as a page

Why might you deliberately avoid writing a default arm in a switch over a sealed type, even though default would make the switch compile?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Leaving out default makes the compiler force you to handle every subtype. If you add a new subtype later, the switch stops compiling and shows you exactly where to update it. A default would hide that.

open as a page

In an exhaustive sealed switch with no default, how are null and an unexpected runtime subtype handled? Explain case null and MatchException.

level: seniorimportance: should knowfreq 38%

basics

~20 s

A null value throws NullPointerException unless you add case null. If, at runtime, a subtype appears that wasn't known when the switch was compiled, the compiler's hidden fallback throws MatchException instead of silently doing nothing.

open as a page

When would you choose a sealed interface over a Java enum, an open class hierarchy, or a visitor pattern - and what evolution risks does sealing introduce?

level: principalimportance: should knowfreq 40%

basics

~20 s

Use a sealed interface when you have a fixed set of distinct shapes that each carry different data - it gives exhaustiveness like an enum but lets each case have its own fields (often as records). Adding a case is a deliberate change that intentionally breaks exhaustive switches.

open as a page

How does sealed-type exhaustiveness in switch compare to enum switch exhaustiveness, and how would you use sealed hierarchies plus exhaustive switches as a domain-modeling technique?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Enums let a switch be exhaustive by covering all constants; sealed types extend that idea to whole class hierarchies, where each subtype can carry its own data. Together with exhaustive switches they model closed sets of cases (sum types) safely.

open as a page