Under The Hood
backend java JDK 21+ (sealed classes JEP 409, pattern matching for switch JEP 441)

Sealed Classes, Records, and Exhaustive Pattern Matching in switch

Last updated
Prerequisites:
Java classes, interfaces, and inheritance
Understanding of switch statements
  • java
  • sealed-classes
  • records
  • pattern-matching
  • switch
  • java-21

Read at your depth

The practical view

sealed classes restrict which classes can extend/implement them: public sealed interface Shape permits Circle, Square, Triangle {}. The permitted subclasses must be in the same module/package (or declared nested). Combined with records (data carriers), you get algebraic-data-type style modeling. Pattern matching for switch lets you write: return switch (shape) { case Circle c -> area(c); case Square s -> area(s); case Triangle t -> area(t); };. The compiler checks exhaustiveness: if you cover all permitted types, no default is needed; if you miss one (or add a new permitted type), the code doesn't compile.

The same idea in other frameworks

react equivalent
N/A — JVM language feature.

Legacy vs modern

instanceof chains vs exhaustive sealed switch

Hand-rolled instanceof chains are unverifiable and need a default; pattern matching on a sealed hierarchy lets the compiler prove completeness.

before → after
instanceof chain with default
if (s instanceof Circle c) return area(c);
else if (s instanceof Square sq) return area(sq); // ❌ OLD: no exhaustiveness check
Exhaustive pattern-matching switch
return switch (s) {
  case Circle c -> Math.PI * c.r() * c.r();
  case Square sq -> sq.side() * sq.side();
  case Triangle t -> 0.5 * t.base() * t.height();
}; // ✅ NEW: compiler-verified exhaustive

Interview gotchas

Context

This tests the language-model understanding — interviewers want the closed-hierarchy property and how the compiler uses the permits list for totality, not just the syntax.

The mechanical answer

A sealed class/interfaces declares a closed set of permitted direct subclasses (via the permits clause, or nested declaration). The compiler enforces that only those types can extend it (across the same module), and the JVM verifies it at runtime through the PermittedSubclasses attribute. Because the hierarchy is closed and known statically, the compiler can enumerate every possible runtime type of a sealed value, so a switch over it can be proven exhaustive: cover all permitted types and no default is needed; miss one, or add a new permitted type, and the code fails to compile. This turns 'forgot a case' runtime bugs into compile-time errors and enables the compiler to generate efficient dispatch without a catch-all.

Trap

The naive answer is 'sealed just restricts inheritance'. The value is the closed-world totality proof it enables for pattern matching. Another trap: assuming permits is optional sugar — a sealed type without an explicit permits clause uses nested-declaration inference, but the rule that subclasses must be co-located (same package or module) still applies, and adding a subclass outside the permits list is a compile error. Candidates who connect sealing to exhaustiveness show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.