Java Sealed Classes
In this page:
Introduction to Sealed Classes
Sealed classes let you explicitly restrict which other classes are allowed to extend or implement them, rather than leaving your hierarchy open to being extended by any unrelated class anywhere in the codebase. This gives you precise, deliberate control over exactly how your inheritance hierarchy can grow.
Example: Introduction to Sealed Classes
public class Main {
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
public static void main(String[] args) {
Shape s = new Circle(3);
System.out.println(s);
}
}
Login to try C/C++/Java/PHP code in the editor
Subclass Constraints
Every direct subclass of a sealed class must itself be declared as one of exactly three things: final (closing that branch off entirely), sealed (continuing to restrict further extension), or non-sealed (reopening that branch to unrestricted extension). This chain of declarations is enforced by the compiler, so the hierarchy's shape can never drift silently.
Example: Subclass Constraints
public class Main {
sealed static class Vehicle permits Car, Truck {}
static final class Car extends Vehicle {} // final -- closes this branch
static non-sealed class Truck extends Vehicle {} // non-sealed -- reopens it
public static void main(String[] args) {
Vehicle v = new Car();
System.out.println(v.getClass().getSimpleName());
}
}
Login to try C/C++/Java/PHP code in the editor
Sealed Classes and Pattern Matching
Sealed classes pair especially well with pattern matching in switch statements, because the compiler already knows the complete, closed list of permitted subclasses. That means it can verify your switch covers every possible case at compile time, without you needing to add a defensive default branch.
Example: Sealed Classes and Pattern Matching
public class Main {
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
static double area(Shape s) {
return switch (s) { // compiler verifies every case is covered, no default needed
case Circle c -> Math.PI * c.radius() * c.radius();
case Square sq -> sq.side() * sq.side();
};
}
public static void main(String[] args) {
System.out.println(area(new Square(4)));
}
}
Login to try C/C++/Java/PHP code in the editor
Reflection and Sealed Classes
You can use reflection to inspect a sealed class at runtime, such as checking whether a given Class is sealed, or dynamically listing out its permitted subclasses via getPermittedSubclasses(). This is mostly useful for frameworks and tooling that need to introspect a sealed hierarchy generically.
Example: Reflection and Sealed Classes
public class Main {
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
public static void main(String[] args) {
System.out.println(Shape.class.isSealed());
for (Class<?> c : Shape.class.getPermittedSubclasses()) {
System.out.println(c.getSimpleName());
}
}
}
Login to try C/C++/Java/PHP code in the editor
Real-World Domain Modeling
Sealed classes are a natural fit for modeling domain hierarchies where you genuinely want to represent a fixed, closed set of states or variants, such as the possible outcomes of a payment (Success, Declined, Pending) rather than an open-ended list anyone could extend later.
Example: Real-World Domain Modeling
public class Main {
sealed interface PaymentResult permits Success, Declined, Pending {}
record Success(String transactionId) implements PaymentResult {}
record Declined(String reason) implements PaymentResult {}
record Pending(String reference) implements PaymentResult {}
public static void main(String[] args) {
PaymentResult result = new Declined("Insufficient funds");
System.out.println(result);
}
}
Login to try C/C++/Java/PHP code in the editor
Chapter Quiz — Complete all 12 topics to unlock
0/12 topics done
Complete these topics first: