Sealed Classes in Java β Complete Guide with Real-World Examples
π Table of Contents
- What are Sealed Classes?
- Why Sealed Classes are Needed (Deep Dive)
- When Were They Introduced?
- How Sealed Classes Work Internally
- Types of Permitted Classes
- Real-World Case Studies
- Implementation in Java (Code Examples)
- Sealed Classes with Records
- Pattern Matching with Sealed Classes
- Best Practices
- When NOT to Use Sealed Classes
- Summary
- Interview Questions
π§ What are Sealed Classes?
Sealed classes let you restrict which classes can extend or implement them.
public sealed class Shape permits Circle, Rectangle {
}
Only Circle and Rectangle can extend Shape.
β Why Sealed Classes are Needed (Deep Dive)
Before sealed classes:
finalblocks inheritanceabstractallows anyone to extend
Real-world systems need more control than this.
Controlled Inheritance Explained
Controlled inheritance means you define exactly which classes can extend your class.
Without Sealed Classes
abstract class Payment {
}
class CreditCard extends Payment {}
class UPI extends Payment {}
class CryptoPayment extends Payment {} // unexpected
Problems:
- Any developer can add new subclasses
- The system can behave unpredictably
With Sealed Classes
public sealed class Payment permits CreditCard, UPI {
}
final class CreditCard extends Payment {}
final class UPI extends Payment {}
class CryptoPayment extends Payment {} // Compile-time error
Now your design stays controlled and predictable.
How Code Readability Improves
public sealed class Shape permits Circle, Rectangle, Triangle {
}
When you read this:
- You see all possible types immediately
- You understand the complete hierarchy
- You donβt need to search the codebase
This approach makes your code self-explanatory.
Business Logic Clarity Example
Without sealed classes:
if (payment instanceof CreditCard) {
// logic
} else if (payment instanceof UPI) {
// logic
} else {
// fallback for unknown types
}
You must handle unknown cases because new subclasses can appear.
With sealed classes:
return switch (payment) {
case CreditCard cc -> "Processing card";
case UPI upi -> "Processing UPI";
};
The compiler checks all cases. You donβt need a default branch.
π When Were Sealed Classes Introduced?
- Java 15 introduced them as a preview
- Java 16 refined them
- Java 17 made them stable (LTS)
βοΈ How Sealed Classes Work Internally
Follow these rules:
- Use the
sealedkeyword - Define allowed subclasses with
permits - Each subclass must declare one of:
finalsealednon-sealed
π§© Types of Permitted Classes
Final Class
final class Circle extends Shape {}
This class cannot be extended further.
Sealed Class
sealed class Rectangle extends Shape permits Square {}
This class continues to control its hierarchy.
Non-Sealed Class
non-sealed class Triangle extends Shape {}
This class allows further inheritance again.
π Real-World Case Studies
Payment System
Requirement: Allow only specific payment methods.
public sealed interface Payment permits CreditCard, UPI, Cash {
String process();
}
final class CreditCard implements Payment {
public String process() {
return "Processing Credit Card";
}
}
final class UPI implements Payment {
public String process() {
return "Processing UPI";
}
}
final class Cash implements Payment {
public String process() {
return "Processing Cash";
}
}
Benefits:
- Developers cannot introduce unexpected payment types
- Business logic stays consistent
Order State Machine
Orders follow a fixed set of states.
public sealed interface OrderState
permits Created, Shipped, Delivered, Cancelled {
}
record Created() implements OrderState {}
record Shipped() implements OrderState {}
record Delivered() implements OrderState {}
record Cancelled() implements OrderState {}
This design helps you:
- Keep states finite
- Prevent invalid states
- Model workflows clearly
API Response Wrapper
A common backend pattern.
public sealed interface ApiResponse<T>
permits Success, Error {
}
public record Success<T>(T data) implements ApiResponse<T> {}
public record Error<T>(String message) implements ApiResponse<T> {}
Usage:
static String handle(ApiResponse<String> response) {
return switch (response) {
case Success<String> s -> "Data: " + s.data();
case Error<String> e -> "Error: " + e.message();
};
}
You cover all cases without a default branch.
π» Implementation in Java (Mixed Example)
public sealed class Vehicle permits Car, Bike {
public abstract void drive();
}
final class Car extends Vehicle {
public void drive() {
System.out.println("Driving Car");
}
}
non-sealed class Bike extends Vehicle {
public void drive() {
System.out.println("Driving Bike");
}
}
class SportsBike extends Bike {
public void drive() {
System.out.println("Driving Sports Bike");
}
}
This example balances control and flexibility.
π Sealed Classes with Records
This combination works naturally.
public sealed interface Result permits Success, Failure {
}
public record Success(String data) implements Result {}
public record Failure(String error) implements Result {}
Benefits:
- You get immutable data structures
- You reduce boilerplate
- You model domains cleanly
π Pattern Matching with Sealed Classes
static String process(Result result) {
return switch (result) {
case Success s -> "Data: " + s.data();
case Failure f -> "Error: " + f.error();
};
}
The compiler ensures you handle every case.
π‘ Key Insights
- Sealed classes make your domain finite and predictable
- They reduce defensive coding
- They improve collaboration by making hierarchies explicit
- They work best with records and pattern matching
β When NOT to Use Sealed Classes
Framework or Library Design
Avoid sealed classes if external systems need to extend your code.
Unknown Future Extensions
Do not restrict the hierarchy if new subclasses may appear later.
Overuse
Use sealed classes only when they add clarity and control.
π‘ Best Practices
- Keep hierarchies small and meaningful
- Prefer sealed interfaces when possible
- Use records for simple data carriers
- Avoid deep inheritance chains
β Summary
- Sealed classes control inheritance
- Java 17 introduced them as a stable feature
- They improve readability and maintainability
- They suit domain modeling very well
- They work effectively with records and pattern matching
π― Interview Questions
1. What problem do sealed classes solve?
They provide controlled inheritance.
2. What does the permits clause do?
It defines allowed subclasses.
3. What modifiers must subclasses use?
final, sealed, or non-sealed.
4. How do sealed classes improve switch statements?
They allow exhaustive checks without a default branch.
5. Can you seal an interface?
Yes.
6. When should you avoid sealed classes?
When you design extensible systems.
7. Why combine sealed classes with records?
You get immutable and concise implementations.
To know more about Java 17 features check out – https://code2java.com/jdk-17-features-step-by-step-guide/
