Java Sealed Classes (Java 17) – Complete Guide, Examples & Best Practices
Since Java 17, sealed classes and interfaces give Java developers a powerful tool to control class hierarchy and inheritance, allowing only selected subclasses. As a Java engineer working on backend and cloud applications for over a decade, I’ve used sealed classes to build safer, predictable, and maintainable domain models.
In this guide, we’ll cover:
What sealed classes are, and why they were introduced
Syntax & examples (basic → advanced)
Real-world use cases from my projects
Common pitfalls and how to avoid them
FAQs that interviewers often ask
Java Sealed Classes were proposed by JEP 360 and delivered in JDK 15 as a preview feature. They were proposed again, with refinements, by JEP 397 and delivered in JDK 16 as a preview feature. This JEP proposes to finalize Sealed Classes in JDK 17, with no changes from JDK 16.
What Are Sealed Classes & Why They Matter
Explain the concept, background, and motivation:
Java allowed free inheritance: any public class could be extended arbitrarily. This is powerful but can lead to unpredictable hierarchies and misuse. Sealed classes address this.
A sealed class or interface restricts which classes or interfaces may extend or implement it, via a
permitsclause. OracleHelps you model domain entities more strictly — ideal for sum-types, finite hierarchies, state machines, and APIs where only certain implementations make sense.
Java’s sealed classes and interfaces make the language smarter and safer by letting you tightly control who can extend or implement a type. Think of it as giving your class a “guest list” — only the classes you approve can inherit from it.
We usually use inheritance for code reuse. If a class already has the logic you need, you simply extend it and reuse its fields and methods instead of writing everything from scratch.
But sometimes, inheritance isn’t just about reuse — it’s about modeling a well-defined domain. Imagine you’re building a graphics library. You create a base Shape class, and you know exactly which shapes—like Circle and Square—should extend it.
What you don’t want is random users of your library creating their own shapes and messing with your design.
That’s where sealed classes help. By sealing Shape, you explicitly list the only classes allowed to extend it. No unexpected subclasses. No accidental misuse. Just clean, predictable, well-structured code.
Goals
This JDK 17 feature java sealed classes will allow the author of a class or interface to control which code is responsible for implementing it.
Provides a more declarative way than access modifiers to restrict the use of a superclass.
Support future directions in pattern matching by providing a foundation for the exhaustive analysis of patterns.
Non-Goals
It is not a goal to provide new forms of access control such as “friends”.
It is not a goal to change
finalin any way, so no need to get confused.
How to Declare Sealed Classes & Interfaces — Syntax & Examples
To seal a class, add the sealed modifier to its declaration. Then, after any extends and implements clauses, add the permits clause. This clause specifies the classes that may extend the sealed class.
For example, the following declaration of Shape specifies three permitted subclasses, Circle, Square, and Rectangle:
public sealed class Shape
permits Circle, Square, Rectangle {
}
Define the following three permitted subclasses, Circle, Square, and Rectangle, in the same module or in the same package as the sealed class:
public final class Circle extends Shape {
public float radius;
}
Square is a non-sealed class. This type of class is explained in Constraints on Permitted Subclasses.
public non-sealed class Square extends Shape {
public double side;
}
public sealed class Rectangle extends Shape permits FilledRectangle {
public double length, width;
}
Rectangle has a further subclass, FilledRectangle:
public final class FilledRectangle extends Rectangle {
public int red, green, blue;
}
Alternatively, you can define permitted subclasses in the same file as the sealed class. If you do so, then you can omit the permits clause:
package com.techshitanshu.geometry;
public sealed class Figure
// The permits clause has been omitted
// as its permitted classes have been
// defined in the same file.
{ }
final class Circle extends Figure {
float radius;
}
non-sealed class Square extends Figure {
float side;
}
sealed class Rectangle extends Figure {
float length, width;
}
final class FilledRectangle extends Rectangle {
int red, green, blue;
}
public sealed class Shape
permits Circle, Rectangle, Square {
// common properties or methods
}
public final class Circle extends Shape {
private double radius;
// ...
}
public non-sealed class Square extends Shape {
private double side;
// ...
}
public sealed class Rectangle extends Shape
permits FilledRectangle {
private double length, width;
// ...
}
public final class FilledRectangle extends Rectangle {
private int red, green, blue;
// ...
}
Sealed Interfaces
public sealed interface Expr
permits ConstantExpr, AddExpr, MulExpr {
int eval();
}
public final class ConstantExpr implements Expr {
private final int value;
public int eval() { return value; }
}
public final class AddExpr implements Expr {
private final Expr left, right;
public int eval() { return left.eval() + right.eval(); }
}
// and so on...
Key Benefits of Java Sealed Classes
Controlled inheritance & better design clarity — prevents arbitrary subclassing and makes class hierarchies explicit.
Stronger compile-time safety & maintainability — compiler ensures only permitted classes extend/implement, reducing future bugs.
Cleaner domain modelling, especially for closed sets — ideal for enums-like type hierarchies (e.g.
Shape,Event,Response,State).Works well with new features like pattern matching & switch expressions — sealed classes help the compiler check exhaustiveness.
Real-World Use Cases (From My Experience & Common Patterns)
State machines / ADTs (Algebraic Data Types) — e.g. response result types:
Success,Error,Loading— only permitted subclasses.Domain modelling — when domain entities are fixed (e.g.
Shape,PaymentStatus,UserRole) sealing prevents invalid subtype creation.API contracts / libraries — as a library author, you don’t want callers to extend classes unpredictably. Sealed classes make APIs safer.
Combining with pattern matching / switch expressions — safer, more maintainable branching logic, with compiler verifying all cases.
Security- sensitive or business-critical code — sealing prevents extension by untrusted subclasses and unintended behaviour.
Like Java sealed classes, to seal an interface, add the sealed modifier to its declaration. Then, after any extends clause, add the permits clause, which specifies the classes that can implement the sealed interface and the interfaces that can extend the sealed interface.
The following example declares a sealed interface named Expr. Only the classes ConstantExpr, PlusExpr, TimesExpr, and NegExpr may implement it:
package com.example.expressions;
public class TestExpressions {
public static void main(String[] args) {
// (6 + 7) * -8
System.out.println(
new TimesExpr(
new PlusExpr(new ConstantExpr(6), new ConstantExpr(7)),
new NegExpr(new ConstantExpr(8))
).eval());
}
}
sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {
public int eval();
}
final class ConstantExpr implements Expr {
int i;
ConstantExpr(int i) { this.i = i; }
public int eval() { return i; }
}
final class PlusExpr implements Expr {
Expr a, b;
PlusExpr(Expr a, Expr b) { this.a = a; this.b = b; }
public int eval() { return a.eval() + b.eval(); }
}
final class TimesExpr implements Expr {
Expr a, b;
TimesExpr(Expr a, Expr b) { this.a = a; this.b = b; }
public int eval() { return a.eval() * b.eval(); }
}
final class NegExpr implements Expr {
Expr e;
NegExpr(Expr e) { this.e = e; }
public int eval() { return -e.eval(); }
}
When to Use Java Sealed Classes — and When to Avoid
Use sealed classes when:
You have a fixed known set of subclasses (closed hierarchy)
You want strict control over inheritance and API boundaries
You are modeling domain entities that should not be extended arbitrarily
Avoid sealed classes when:
You expect the hierarchy to evolve frequently — adding new subclasses means modifying the
permitsclause.When over-restricting inheritance hampers flexibility or extensibility.
For very simple classes where sealing adds unnecessary complexity.
Common Mistakes & Pitfalls to Watch Out For
Permitted subclasses must be visible to the compiler — same module or package, else compile-time error.
Every subclass must declare a modifier —
final,sealed, ornon-sealed. Omitting leads to compile errors.Sealed is not final — you still control whether descendants can continue the hierarchy. Misunderstanding this can cause design problems.
Not a replacement for all cases — overuse can make code less flexible when requirements change.
Example: Java Sealed Classes in a Project (Code + Explanation)
public sealed interface ApiResponse permits SuccessResponse, ErrorResponse, Loading {
}
public final class SuccessResponse implements ApiResponse {
private final String data;
public SuccessResponse(String data) { this.data = data; }
public String getData() { return data; }
}
public final class ErrorResponse implements ApiResponse {
private final String error;
public ErrorResponse(String error) { this.error = error; }
public String getError() { return error; }
}
public final class Loading implements ApiResponse {}
void handle(ApiResponse res) {
if (res instanceof SuccessResponse s) {
System.out.println("Data: " + s.getData());
} else if (res instanceof ErrorResponse e) {
System.err.println("Error: " + e.getError());
} else {
System.out.println("Loading...");
}
}
Here, you know at compile time: only these three implementations exist — safe, predictable, easy to maintain.
FAQ: Java Sealed Classes in JDK 17 (10 Questions + Answers)
1. What are sealed classes in Java?
Sealed classes are a feature introduced in Java 17 that allow developers to control which other classes or interfaces may extend or implement them. This helps enforce stricter inheritance rules and improves API design and security.
2. Why were sealed classes introduced in Java?
They were introduced to give developers more control over inheritance, reduce accidental misuse of APIs, and offer a middle ground between final and fully open inheritance models.
3. How do you declare a sealed class?
You use the sealed keyword and specify permitted subclasses using the permits clause:
public sealed class Shape permits Circle, Square {}
4. What is the difference between sealed, non-sealed, and final classes?
sealed → inheritance allowed only for permitted subclasses
non-sealed → fully open for anyone to extend
final → cannot be extended further
5. Do all permitted subclasses need a modifier?
Yes. All subclasses must explicitly declare:
final, orsealed, ornon-sealed
If they don’t, the code will not compile.
6. Can sealed classes help improve security?
Yes. By limiting which classes can extend a base class, sealed classes prevent unauthorized or unexpected extension, reducing security risks in sensitive applications and frameworks.
7. Can sealed classes be used with interfaces?
Yes. Interfaces in Java 17 can also be sealed, allowing you to restrict which classes implement them.
8. Are sealed classes compatible with records?
Absolutely. Records can be used as permitted subclasses because they are implicitly final, making them great for modeling controlled data hierarchies.
9. Do sealed classes work with switch expressions?
Yes. When sealed classes define a closed hierarchy, switch expressions can become exhaustive—improving compiler checks and reducing runtime errors.
10. Are sealed classes useful in real-world applications?
Yes. They are widely used in domains like:
domain-driven design
financial systems
microservices contracts
API frameworks
data modeling (Shape → Circle, Rectangle…)
They simplify code maintenance and improve class hierarchy readability.
👉 Check out my guides on Java Lambda Expressions, Generics, and Pattern Matching.


Leave a Reply