Use sealed classes and interfaces to define closed type hierarchies and exhaustive pattern matching.
Published February 25, 2025
Sealed classes let you restrict which classes can extend or implement a class/interface. Combined with pattern matching, they enable exhaustive type-safe processing of a closed set of variants — similar to Rust enums or Kotlin sealed classes.
// The sealed class lists its permitted subtypes
public sealed class Shape
permits Circle, Rectangle, Triangle {
public abstract double area();
}
// Each permitted type must be: final, sealed, or non-sealed
public final class Circle extends Shape {
public Circle(double radius) { this.radius = radius; }
private final double radius;
public double area() { return Math.PI * radius * radius; }
}
public final class Rectangle extends Shape {
public Rectangle(double w, double h) { this.width = w; this.height = h; }
private final double width, height;
public double area() { return width * height; }
}
public non-sealed class Triangle extends Shape {
// non-sealed: allows further extension by anyone
public double area() { return 0; /* simplified */ }
}
public sealed interface Result<T>
permits Result.Success, Result.Failure {
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error, Throwable cause) implements Result<T> {}
}
// Usage
Result<User> result = userService.findUser(id);
if (result instanceof Result.Success<User> s) {
return s.value();
} else if (result instanceof Result.Failure<User> f) {
throw new RuntimeException(f.error());
}
Sealed classes enable exhaustive switch expressions — the compiler verifies all permitted types are handled.
public double calculateArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
// No default needed! Compiler knows all subtypes.
};
}
If you add a new permitted type to Shape, the compiler forces you to handle it in every exhaustive switch — preventing missed cases.
// Payment types as a sealed hierarchy
public sealed interface Payment
permits CreditCard, BankTransfer, Crypto {
record CreditCard(String cardNumber, String cvv) implements Payment {}
record BankTransfer(String iban, String bic) implements Payment {}
record Crypto(String walletAddress, String currency) implements Payment {}
}
// Exhaustive processing
public void processPayment(Payment payment) {
switch (payment) {
case CreditCard cc -> chargeCard(cc.cardNumber(), cc.cvv());
case BankTransfer bt -> initTransfer(bt.iban(), bt.bic());
case Crypto crypto -> sendCrypto(crypto.walletAddress(), crypto.currency());
}
}
final, sealed, or non-sealednon-sealed reopens the hierarchy — useful for extension points