Using Java 17 Sealed Classes to Model a Closed Domain Safely
Learn how Java 17 sealed classes restrict inheritance, enable exhaustive switch statements, and improve domain modeling—complete with a compile‑time example and practical limitations.
06 Jun 2026, 05:04 UTC

Problem: accidental subclasses break domain invariants
When modeling a fixed set of concepts—like the shapes that a graphics library can render—you want to guarantee that only the intended types exist. If a developer later adds a new subclass, existing switch statements may miss a case, leading to runtime bugs or the need for a defensive default clause that hides the real issue.
Thesis: sealed classes give the compiler a way to enforce a closed hierarchy and enable exhaustive pattern matching
By marking a class or interface with the sealed keyword and listing the allowed subtypes in a permits clause, you make the inheritance contract explicit at compile time. The Java compiler then:
- Rejects any attempt to extend the sealed type with a class not listed in
permits. - Allows a
switchover the sealed type to omit adefaultcase when all permitted subtypes are covered, turning missing‑case bugs into compile‑time errors.
Worked example: a sealed shape hierarchy
First, define the sealed base type and its permitted subclasses:
// Shape.java
public sealed class Shape permits Circle, Rectangle {
// common behavior could go here
}
// Circle.java
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
public double radius() { return radius; }
}
// Rectangle.java
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
public double width() { return width; }
public double height() { return height; }
}
Compile with a source level of 17 or higher:
javac -source 17 Shape.java Circle.java Rectangle.java
Now try to add an unauthorized subclass:
// Triangle.java (will not compile)
public class Triangle extends Shape { }
The compiler emits:
Triangle.java:1: error: class Triangle is not allowed to extend sealed class Shape
public class Triangle extends Shape { }
^
1 error
Exhaustive switch without a default
Because the hierarchy is sealed, the compiler can verify that all cases are handled:
// AreaCalculator.java
public class AreaCalculator {
public static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
// No default needed; omitting a case would cause a compile error
};
}
}
Compile the calculator:
javac -source 17 AreaCalculator.java
If a new permitted subclass is added (e.g., Triangle), the switch will fail to compile until you add its case, preventing missed‑case bugs at runtime.
Trade‑offs and limitations
- Not combinable with
final: a sealed class must be inheritable by at least one permitted subclass, so you cannot make it both sealed and final. - Reflection can bypass the seal: code that uses
Class.forNameorUnsafe.defineClasscan still create subclasses at runtime. For security‑critical boundaries, sealing alone is insufficient; you must also enforce checks via a security manager or module system. - Module visibility: the
permitslist is checked at compile time, but if a permitted subclass lives in a different module, that module must export the package containing the class.
When to reach for sealed classes
Use sealed types when you model a domain with a known, finite set of variations—such as expression trees, protocol messages, or UI components—and you want the compiler to help you keep that set closed. If the hierarchy is expected to grow unpredictably or you need extensibility via third‑party plugins, keep the base type open and rely on tests or documentation to manage extensions.
Actionable takeaway
Start by marking your base class with sealed and listing the current subtypes in permits. Compile with -source 17 (or later) and let the compiler warn you whenever you attempt to add an unlisted subclass or forget a case in a switch. Treat any reflection‑based bypass as a separate security concern, not a flaw in the sealing mechanism itself.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.