sealed on a class or interface, combined with a permits clause listing the allowed direct subtypes, restricts inheritance to exactly that named set — unlike an ordinary Java class, which any code anywhere can extend. Each permitted subclass must itself be declared final, sealed (with its own further-restricted permits list), or non-sealed (explicitly reopening it to unrestricted extension). Because the compiler now has complete, closed knowledge of every possible subtype, a switch over a sealed type's subtypes can be verified exhaustive at compile time, with no default branch required.
Declaring a sealed hierarchy
Any class outside this permits list attempting to extend Shape is a compile error — the hierarchy is closed by design, not just by convention.
public sealed interface Shape permits Circle, Square, Triangle { }
public final class Circle implements Shape {
public double radius;
}
public final class Square implements Shape {
public double side;
}
public final class Triangle implements Shape {
public double base, height;
}
// public class Hexagon implements Shape { } // COMPILE ERROR: not in permits listEach subclass must declare its own inheritance status
final closes it completely (Circle above); sealed continues the restriction one level further; non-sealed deliberately reopens that one branch to unrestricted extension — every permitted subclass must pick exactly one of these three.
public sealed interface Shape permits Circle, Square, CustomShape { }
public final class Circle implements Shape { } // closed completely
public non-sealed interface CustomShape extends Shape { } // reopened — anyone can implement this branchExhaustive pattern matching: no default needed
Because the compiler knows Circle, Square, and Triangle are the ONLY possible subtypes, a switch covering all three is verified exhaustive at compile time — adding a fourth permitted subtype later would make this switch a compile error until updated, catching an easily-missed case immediately rather than silently at runtime.
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius * c.radius;
case Square s -> s.side * s.side;
case Triangle t -> 0.5 * t.base * t.height;
// no default needed — compiler verifies these 3 cases are ALL of them
};
}