Introduction
Object-Oriented Programming is built around inheritance.
For decades, Java allowed any class to extend another class unless it was declared final.
This gave developers only two choices:
Open for everyone
or
Closed for everyone
There was no middle ground.
However, enterprise applications often contain business models where only a fixed set of subtypes should ever exist.
Examples include:
- Payment Methods
- Notification Channels
- Account Types
- Loan Types
- Shipping Methods
- Insurance Policies
- Authentication Providers
- Workflow Commands
Consider a payment system.
Payment
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Credit Card UPI Net Banking
Should another developer be allowed to create:
CryptoMonkeyPayment
Probably not.
The business itself defines the valid payment methods.
The language should be able to express that rule.
Java 17 introduced Sealed Classes, allowing developers to explicitly declare which classes may extend a superclass or implement an interface.
This makes domain models safer, easier to understand, and easier for the compiler to analyze.
Learning Objectives
By the end of this article, you will be able to:
- Understand why Sealed Classes were introduced.
- Create sealed classes and interfaces.
- Use
permits. - Understand
final,sealed, andnon-sealed. - Design finite business hierarchies.
- Combine Records with Sealed Classes.
- Prepare for Pattern Matching for
switch.
The Problem Before Java 17
Suppose we define:
public abstract class Payment {
}
Anyone can create:
public class CryptoPayment
extends Payment {
}
or
public class TestPayment
extends Payment {
}
The compiler has no way to enforce business constraints.
Java 17 Solution
public sealed class Payment
permits
CreditCardPayment,
UpiPayment,
NetBankingPayment {
}
Now only those three classes may extend Payment.
Any other subclass results in a compilation error.
What Is permits?
The permits clause explicitly lists all allowed subclasses.
public sealed interface Notification
permits
EmailNotification,
SmsNotification,
PushNotification {
}
The hierarchy becomes part of the language itself.
Every Child Must Choose
Every permitted subclass must explicitly declare one of three modifiers.
1. final
public final class UpiPayment
extends Payment {
}
No further inheritance is allowed.
2. sealed
public sealed class CardPayment
extends Payment
permits
VisaPayment,
MasterCardPayment {
}
The hierarchy continues but remains controlled.
3. non-sealed
public non-sealed class CustomPayment
extends Payment {
}
Inheritance becomes unrestricted again from this point onward.
Visualizing the Hierarchy
Payment (sealed)
│
┌───────────────┼───────────────┐
▼ ▼ ▼
CreditCard UPI(final) NetBanking(final)
│
▼
Visa
MasterCard
The hierarchy is explicit and finite.
Enterprise Example – Payment Gateway
public sealed interface PaymentMethod
permits
CardPayment,
UpiPayment,
WalletPayment {
}
The business determines all supported payment methods.
Developers cannot accidentally introduce unsupported implementations.
Enterprise Example – Notification System
public sealed interface Notification
permits
EmailNotification,
SmsNotification,
PushNotification {
}
Notification processing becomes more predictable because the compiler knows every valid subtype.
Enterprise Example – Banking
public sealed interface Account
permits
SavingsAccount,
CurrentAccount,
LoanAccount {
}
The banking domain becomes self-documenting.
Enterprise Example – Workflow Engine
WorkflowCommand
↓
Approve
Reject
Escalate
Using a sealed hierarchy ensures that only valid workflow commands exist.
Combining Records and Sealed Classes
One of the most powerful modern Java patterns is:
public sealed interface Payment
permits
CardPayment,
UpiPayment {
}
Each implementation can be a Record.
public record CardPayment(
String cardNumber,
BigDecimal amount)
implements Payment {
}
public record UpiPayment(
String upiId,
BigDecimal amount)
implements Payment {
}
This combination provides:
- Immutable data
- Restricted inheritance
- Strong compiler guarantees
Why Is This Important?
Suppose a service processes payments.
Traditional approach:
Payment
↓
Unknown subclasses
↓
Runtime surprises
Modern Java:
Payment
↓
Known subclasses
↓
Compiler verification
The compiler now understands the complete business model.
Sealed Classes vs final
Many developers ask:
“If final already prevents inheritance, why do we need Sealed Classes?”
final | sealed |
|---|---|
| No subclasses | Selected subclasses |
| Completely closed | Controlled hierarchy |
| Single type | Family of related types |
Sealed Classes provide controlled extensibility rather than eliminating inheritance entirely.
Sealed Interfaces
Sealed interfaces are often even more useful than sealed classes.
Example:
public sealed interface Event
permits
OrderCreated,
PaymentReceived,
ShipmentCompleted {
}
Events become a well-defined contract.
Spring Boot Example
REST request:
PaymentRequest
↓
CardPayment
↓
UPIPayment
Using sealed hierarchies improves validation and makes business logic easier to reason about.
Domain-Driven Design
Sealed Classes fit naturally into Domain-Driven Design.
Examples include:
- Commands
- Events
- Policies
- Value hierarchies
- State machines
The compiler helps enforce the ubiquitous language of the domain.
Performance
Sealed Classes are primarily a language modeling feature.
Their main advantages are:
- Better readability
- Stronger type safety
- Improved compiler analysis
Performance differences are generally negligible.
Common Mistakes
Sealing Everything
Not every hierarchy should be sealed.
Use sealing only when the set of valid subtypes is intentionally finite.
Using Sealed Classes for Plugin Architectures
Plugin systems often require third-party extensions.
Sealed hierarchies would unnecessarily restrict that flexibility.
Forgetting Child Modifiers
Every permitted subclass must declare:
finalsealed- or
non-sealed
The compiler enforces this rule.
Best Practices
✔ Seal finite business hierarchies.
✔ Use Records for immutable implementations.
✔ Keep the list of permitted subclasses focused.
✔ Prefer sealed interfaces for contracts.
✔ Avoid sealing extension points intended for external developers.
Interview Questions
Why were Sealed Classes introduced?
To allow developers to explicitly control which classes may extend a superclass or implement an interface.
What is the purpose of the permits clause?
It lists the only classes that are allowed to inherit from a sealed class or implement a sealed interface.
What modifiers can a permitted subclass use?
final, sealed, or non-sealed.
When should Sealed Classes be used?
When the business domain defines a fixed and known set of valid subtypes.
Can Records implement a sealed interface?
Yes.
In fact, Records and Sealed Interfaces work exceptionally well together for modeling immutable domain hierarchies.
Summary
Sealed Classes give Java something it lacked for decades: the ability to model finite inheritance hierarchies directly in the language. They allow developers to express business constraints that previously existed only in documentation or code reviews.
Combined with Records and Pattern Matching, Sealed Classes form the foundation of Java’s modern type system. The compiler gains a much deeper understanding of your domain, enabling safer code, better tooling, and more expressive language features.
Coming Up Next
Part 36 – Java 17: Pattern Matching for switch – The Future of Decision Making in Java
We’ll bring together everything you’ve learned so far:
- Switch Expressions
- Pattern Matching
- Records
- Sealed Classes
You’ll learn how modern Java replaces long if-else chains and complex instanceof logic with concise, type-safe switch expressions that understand object hierarchies. This is one of the most exciting language features in Java 21 and represents the culmination of several releases of language evolution.