Sealed Classes in Java


Sealed classes are one of the biggest “modern Java” features that came after Java 8, finalized in Java 17 (LTS). They give you more control over inheritance and make class hierarchies safer and easier to reason about.
1. The problem they solve
In Java 8, if you wrote an abstract class or interface, any class in the world could extend/implement it (unless you made the constructor/package-private). That meant:
You couldn’t always predict or restrict the hierarchy.
Exhaustive
switch
orinstanceof
checks weren’t possible (because subclasses could be added anywhere).This made reasoning, pattern matching, and security harder.
2. What sealed classes do
With a sealed class (or interface), you explicitly list which classes are allowed to extend/implement it.
Syntax:
public sealed class Shape
permits Circle, Rectangle, Square { }
public final class Circle extends Shape { }
public non-sealed class Rectangle extends Shape { }
public sealed class Square extends Shape permits ColoredSquare { }
Keywords:
sealed → restricts which classes can extend it (must be in same package or module).
permits → lists the allowed subclasses.
final → subclass can’t be extended further.
sealed → subclass itself continues the sealing, listing its own permitted subclasses.
non-sealed → subclass lifts the restriction, allowing free extension again.
3. Non-Sealed Classes in Java
When you define a sealed class or interface (Java 17+), you explicitly list which classes are allowed to extend/implement it.
But not every subclass needs to remain sealed or final.
That’s where non-sealed
comes in:
It removes the restriction and allows any other class to extend it.
Useful when you want to control the top-level hierarchy but leave some branches open for extensibility.
Example:-
Sealed interface
sealed interface Shape permits Circle, Rectangle, Polygon { }
Sealed and final implementations
final class Circle implements Shape { }
final class Rectangle implements Shape { }
Non-sealed implementation
non-sealed class Polygon extends Shape { }
class Triangle extends Polygon { }
class Hexagon extends Polygon { }
Here:
Shape
is sealed → onlyCircle
,Rectangle
, andPolygon
are allowed to implement it.Circle
andRectangle
are final → no further subclassing.Polygon
is non-sealed → other classes are free to extend it, even outside the original permits list.
4. Do subclasses of a sealed class need final
/ sealed
/ non-sealed
?
Yes, it’s mandatory.
Every direct subclass of a sealed
class (or sealed
interface) must explicitly declare one of the following modifiers:
final
→ no further subclassing allowed.sealed
→ subclass is also sealed, and must declare its ownpermits
list.non-sealed
→ subclass removes restrictions, allowing free subclassing again.
Why this rule?
Ensures clarity of hierarchy → the compiler knows if the subclass hierarchy is closed, open, or final.
Makes exhaustive pattern matching possible in
switch
.Prevents ambiguity (you always know whether further extension is possible).
If you omit final
, sealed
, or non-sealed
in a permitted subclass, the compiler throws an error.
5. Why it matters
Safer modeling: You can encode business rules in the type system. Example: A
PaymentMethod
sealed to onlyCard
,UPI
,Wallet
.Exhaustive checks: With pattern matching for
switch
(Java 21), the compiler can verify you’ve covered all cases of a sealed hierarchy.Better readability & maintainability: Readers instantly know the closed set of implementations.
6. Example with pattern matching
sealed interface PaymentMethod permits Card, UPI, Wallet { }
record Card(String number) implements PaymentMethod { }
record UPI(String id) implements PaymentMethod { }
record Wallet(String provider) implements PaymentMethod { }
public String process(PaymentMethod p) {
return switch (p) {
case Card c -> "Processing card " + c.number();
case UPI u -> "Processing UPI " + u.id();
case Wallet w -> "Processing wallet " + w.provider();
}; // compiler ensures this switch is exhaustive
}
7. Where you’ll use them
Modeling fixed domain hierarchies (payment types, shapes, states in a workflow, commands/events).
Working with pattern matching (switch + records).
Replacing “enums with data”: instead of an enum plus multiple unrelated data holders, you can model each as a sealed subtype.
Subscribe to my newsletter
Read articles from Mohit jain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Mohit jain
Mohit jain
Oracle Certified Java Developer with 7+ years of experience specializing in backend development, microservices architecture, and cloud-based solutions. Proven expertise in designing scalable systems, optimizing performance, and mentoring teams to enhance productivity. Passionate about building high-performance applications using Java, Spring Boot, Kafka, and cloud technologies (AWS/GCP)