On this page
Sealed Classes
Sealed classes and interfaces (since Java 17) restrict which classes or interfaces may extend or implement them. This enables exhaustive pattern matching and clearer domain modeling.
Sealed Class
public sealed class Shape
permits Circle, Rectangle, Triangle {
// common behavior
}
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
public double radius() { return radius; }
}
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
}
public non-sealed class Triangle extends Shape {
// non-sealed: further subclasses are allowed
}
Permitted Subclass Modifiers
Every permitted subclass must be one of:
| Modifier | Meaning |
|---|---|
final |
Cannot be extended further |
sealed |
Must declare its own permits clause |
non-sealed |
Open for further extension |
Sealed Interfaces
public sealed interface Payment
permits CreditCard, BankTransfer, Crypto { }
public record CreditCard(String number, String cvv) implements Payment { }
public record BankTransfer(String iban) implements Payment { }
public record Crypto(String walletAddress) implements Payment { }
Pattern Matching with Sealed Types
Sealed hierarchies enable exhaustive switch:
public double area(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 verifies exhaustiveness
}
If a new subclass is added to the sealed hierarchy, the compiler flags any non-exhaustive switch.
Sealed Classes vs Final vs Abstract
| Feature | final |
abstract |
sealed |
|---|---|---|---|
| Can be extended | No | Yes (by anyone) | Yes (only permitted types) |
| Can be instantiated | Yes | No | Depends on subclass |
| Use case | Prevent extension | Define template | Controlled hierarchy |
Real-World Use Cases
- AST nodes in compilers or interpreters
- Result types:
Success | Failure | Pending - Domain events with known variants
- API response types with fixed shapes
public sealed interface Result<T> permits Success, Failure {
record Success<T>(T value) implements Result<T> { }
record Failure<T>(String error) implements Result<T> { }
}
Best Practices
- Use sealed types when the set of variants is fixed and known
- Combine with records for concise variant definitions
- Prefer
finalpermitted subclasses unless further extension is intentional - Use exhaustive
switchinstead ofinstanceofchains when possible