Skip to main content

Sealed Hierarchies & Exhaustive Switches

intermediate13 min readLesson 68 of 180

Making the variant set finite so the compiler turns forgotten cases into build errors.

Sealed hierarchies and exhaustive switches

Before sealed classes, an interface had unbounded implementations, so a switch over its subtypes always needed a default. Sealed interfaces make the set of implementations finite and compiler-checked:

public sealed interface Shape permits Circle, Rect {}
public record Circle(double r) implements Shape {}
public record Rect(double w, double h) implements Shape {}

static double area(Shape s) {
    return switch (s) {                 // pattern matching for switch
        case Circle c -> Math.PI * c.r() * c.r();
        case Rect r  -> r.w() * r.h();
    };                                  // no default — compiler knows all cases
}

Add a third permit without updating the switch and the code does not compile — the compiler turns a forgotten case into a build error instead of a runtime surprise.

Choose sealed when the set of variants is your domain decision (shapes, payment methods, parse results). Keep interfaces open when third parties should be able to plug in.