Sealed Classes and Interfaces in Java (Java 17+)

A sealed class or interface restricts which other classes can extend or implement it. You list them with permits. Subclasses must themselves be final, sealed, or non-sealed. Sealed hierarchies are the foundation for exhaustive switches (Java 21+) β€” the compiler can prove you've handled every case.

Syntax

public sealed interface Shape permits Circle, Square, Triangle {}

public final  record Circle(double r)   implements Shape {}
public final  record Square(double s)   implements Shape {}
public final  record Triangle(double b, double h) implements Shape {}

What "sealed" really buys you

String describe(Shape s) {
    return switch (s) {
        case Circle c   -> "circle " + c.r();
        case Square sq  -> "square " + sq.s();
        case Triangle t -> "triangle " + t.b() + "x" + t.h();
        // no default needed β€” the compiler knows the list is complete
    };
}

Add a new permitted type and the switch becomes a compile error until you handle it. That's the point.

Subclass modifiers

  • final β€” closes the hierarchy at this subclass. Records are implicitly final.
  • sealed β€” further restricts with its own permits clause.
  • non-sealed β€” opens this branch back up; anyone can extend it.
public sealed class Vehicle permits Car, Truck, SpecialVehicle {}
public final  class Car     extends Vehicle {}
public non-sealed class SpecialVehicle extends Vehicle {}  // open β€” anyone can subclass

public sealed class SportsCar extends Car permits Ferrari {} // ❌ Car is final

Same package/module rule

All permitted subclasses must be in the same module (or, if not modularised, the same package). This is what makes sealing reliable β€” you can see the full hierarchy from one place.

When to seal

  • Algebraic data types β€” an expression tree, a result that is either Ok or Err.
  • State machines β€” a finite set of states you want to handle exhaustively.
  • Public APIs with a known closed set β€” a library author wants to prevent users from adding new variants.

Common mistakes

  • Forgetting permits when the subclasses aren't in the same file β€” compile error.
  • Mixing default with sealed-type switch β€” often unnecessary; the compiler proves exhaustiveness without it. Removing the default re-enables the compile error when a new variant is added.
  • Sealing an open ecosystem β€” if external code must add variants, sealed is wrong. Use a normal interface.

Related

Pillar: OOP. Siblings: records, interfaces, switch pattern matching.