Comprehensive Guide to Sealed Classes & Interfaces in Java
Target versions:
- Preview: Java 15 (JEP 360), Java 16 (JEP 397)
- Final / Standard: Java 17 (JEP 409)Sealed classes and interfaces let you control which other classes or interfaces may extend or implement them.
They are essential for modeling closed hierarchies and work perfectly with records and pattern matching.
1. What Are Sealed Classes / Interfaces?
A sealed class or interface:
- Explicitly controls which classes are allowed to extend / implement it.
- Declares a
permitsclause listing all permitted subclasses. - Ensures the type hierarchy is closed and well-defined at compile-time.
Example:
public sealed interface Shape
permits Circle, Rectangle, Square {}
public final class Circle implements Shape {
// ...
}
public final class Rectangle implements Shape {
// ...
}
public final class Square implements Shape {
// ...
}
Here:
Shapeis sealed.- Only
Circle,Rectangle, andSquaremay implementShape.
2. Why Sealed Types? (Motivation)
Before sealed types:
- Any class could extend a non-final class or implement an interface.
- Frameworks and APIs often relied on documentation or package-private tricks to limit extension.
- Exhaustive reasoning over class hierarchies (e.g., in
switch) was hard.
Sealed types:
- Restrict inheritance: you decide which classes belong to the hierarchy.
- Make code safer: no unexpected subclasses from other modules/packages.
- Enable better pattern matching: compiler can check exhaustiveness in
switchon sealed types. - Model algebraic data types (ADTs) more naturally (often with records).
3. Basic Syntax
3.1. Sealed Interface
public sealed interface Shape
permits Circle, Rectangle, Square {}
3.2. Sealed Class
public sealed abstract class Result
permits Success, Failure {}
3.3. Permitted Subclasses – 3 Options
A permitted subclass must be declared as one of:
final– cannot be subclassed furthersealed– further restricted hierarchynon-sealed– re-opens the hierarchy from that point
Example:
public sealed class Parent permits Child1, Child2, Child3 {}
public final class Child1 extends Parent {} // no further subclasses
public sealed class Child2 extends Parent // can be extended, but controlled
permits GrandChildA, GrandChildB {}
public non-sealed class Child3 extends Parent {} // open for extension
4. Rules for Permitted Subclasses
For a sealed class or interface S:
- Each permitted subclass must:
- Be in the
permitsclause ofS, or - (In some cases) be in the same compilation unit and automatically discovered (JEP 409 refinement).
- Each permitted subclass must:
- Directly extend / implement
S. - Each permitted subclass must be declared as:
final,sealed, ornon-sealed.- Permitted subclasses must be in:
- The same module (if using modules), or
- The same package, if not in a module.
Otherwise, you get compile-time errors.
5. Example: Modeling a Closed Shape Hierarchy
public sealed interface Shape
permits Circle, Rectangle, Square {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Square(double side) implements Shape {}
Shapeis sealed: only the listed records implement it.- No other class in your project can implement
Shape.
6. Sealed Hierarchies: final, sealed, non-sealed
6.1. final – Leaf Node
public final class Circle implements Shape {}
- Cannot be subclassed further.
- Good for simple leaf types.
6.2. sealed – Continue Restricting
public sealed class Animal permits Dog, Cat {}
public sealed class Dog extends Animal
permits Bulldog, Labrador {}
public final class Bulldog extends Dog {}
public final class Labrador extends Dog {}
You can build multi-level closed hierarchies.
6.3. non-sealed – Reopen Hierarchy
public sealed class Animal permits Dog, Cat, Other {}
public non-sealed class Other extends Animal {}
Otheris a permitted subclass ofAnimal, but:- No longer sealed — any class can extend
Other(subject to normal access rules). - Useful when you want partial control:
- Some branches are closed,
- Some branches open to extension.
7. Alternative: Omitting permits in Same-File Declarations
If all permitted subclasses are declared in the same source file as the sealed type, you can omit the permits clause (from Java 17’s final design).
Example:
public sealed interface Shape {
// no permits here
}
final class Circle implements Shape {}
final class Rectangle implements Shape {}
final class Square implements Shape {}
The compiler automatically infers the permitted subclasses from that compilation unit.
8. Interaction with Access Modifiers
Access rules still apply:
- You can have
public sealed,protected sealed, package-privatesealed, etc. - The permitted subclasses must obey access constraints.
Example:
sealed class InternalType permits InternalImpl {}
final class InternalImpl extends InternalType {}
Both in same package (no public), usage is restricted to the package.
9. Sealed vs final vs abstract vs enum
9.1. final Class
- No subclasses allowed at all.
- Overkill if you want a limited set of subclasses (not zero).
9.2. abstract Class
- Cannot be instantiated.
- Subclasses are unrestricted unless combined with
sealed.
9.3. sealed Class or Interface
- Controlled set of subclasses.
- Combine with
abstractorinterfaceto build closed families.
9.4. enum
- Fixed set of constants of a single type.
- Good for simple finite value sets (e.g.,
RED,GREEN,BLUE). - Sealed hierarchies are more expressive:
- Can have multiple types (classes/records) representing different shapes of data.
- Excellent for Algebraic Data Types (ADTs).
10. Pattern Matching + Sealed Types (Huge Benefit)
Sealed types pair perfectly with pattern matching and switch expressions.
10.1. Switch Expression Over Sealed Types
public sealed interface Shape permits Circle, Rectangle, Square {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Square(double side) implements Shape {}
double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Square sq -> sq.side() * sq.side();
};
}
Notes:
- No default needed; the compiler knows
Shapeis sealed and all implementations are covered. - If you add a new subtype of
Shape, thisswitchwill no longer compile until updated.
10.2. Combining with Record Patterns
double area(Shape s) {
return switch (s) {
case Circle(double radius) -> Math.PI * radius * radius;
case Rectangle(double w, double h) -> w * h;
case Square(double side) -> side * side;
};
}
This is extremely concise and expressive.
11. Example: Result / Either Type
Define a sealed abstraction for computation results:
public sealed interface Result<T>
permits Success, Failure {}
public record Success<T>(T value) implements Result<T> {}
public record Failure<T>(String message) implements Result<T> {}
Usage:
Result<String> res = compute();
String msg = switch (res) {
case Success(String value) -> "OK: " + value;
case Failure(String message) -> "ERROR: " + message;
};
12. Practical Rules & Limitations
12.1. Permitted Subclasses Location
- Must be in the same module, if modules used.
- Or the same package when not using modules.
Cannot permit a subclass in a different module (for sealed type defined in another module).
12.2. Each Permitted Subclass Must Declare Modifer
Each permitted subclass of a sealed type must be explicitly:
final, orsealed, ornon-sealed
Example (illegal):
public sealed interface Shape permits Circle {}
class Circle implements Shape {} // ❌ must declare final / sealed / non-sealed
Correct:
public final class Circle implements Shape {}
12.3. Sealed Type Must List Permitted Subclasses (Or Share File)
If you use a permits clause, you must list all permitted subclasses.
If you don’t want to list them explicitly:
- You must declare them in the same source file as the sealed type.
12.4. Changing the Hierarchy Requires Updates
If you:
- Add a new permitted subclass → must:
- Update the
permitsclause (if used), - Fix all exhaustive
switchexpressions if needed. - Remove a permitted subclass → must:
- Update the
permitsclause, - Possibly update
switches.
This is intentional: it makes hierarchies explicit and safe.
12.5. Reflection API Adjustments
Class has methods to inspect sealed-ness:
Class<?> c = Shape.class;
if (c.isSealed()) {
Class<?>[] permitted = c.getPermittedSubclasses();
}
You can inspect sealed hierarchies at runtime.
13. Example: Multiple Levels and Non-Sealed Branches
public sealed interface Expr permits Num, Binary {}
public record Num(int value) implements Expr {}
public sealed interface Binary extends Expr
permits Add, Mul {}
public record Add(Expr left, Expr right) implements Binary {}
public non-sealed class Mul implements Binary {
private final Expr left;
private final Expr right;
public Mul(Expr left, Expr right) {
this.left = left;
this.right = right;
}
public Expr left() { return left; }
public Expr right() { return right; }
}
Exprhas 2 branches:NumandBinary.Binaryis itself sealed, butMulisnon-sealed, allowing further subclasses.
14. Sealed Classes vs Traditional Design Patterns
Sealed types help you implement:
- Visitor pattern in a more natural way.
- ADTs used in functional programming (e.g.,
Option,Either,Result, expression trees).
Instead of:
- Open hierarchies + visitors + fragile switch on
instanceof, - You can rely on compiler-checked exhaustiveness.
15. Style Guidelines & Best Practices
✔ Use Sealed Types When:
- You want a closed set of subclasses.
- You control the entire hierarchy (e.g., inside a library or module).
- You want to use pattern matching with exhaustiveness.
- You’re modeling ADTs/domain hierarchies.
✔ Combine With:
- Records – for leaf data types.
- Pattern Matching for
switch– for expressive branching. - Record Patterns – for deconstruction.
❌ Don’t Use Sealed Types When:
- You expect users to extend your type freely outside your module.
- The set of subtypes is not naturally closed.
- It’s purely a utility type with no structured subtyping semantics.
Additional Tips
- Favor
sealed interface+ record implementations for algebraic types. - Use
non-sealedsparingly: it reduces strictness. - Keep the hierarchy readable and small.
16. Typical Interview Questions (With Short Answers)
Q1. What is a sealed class/interface in Java?
A type that restricts which classes may extend or implement it, via a permits clause or same-file inferred subclasses.
Q2. In which version did sealed classes become standard?
Java 17.
Q3. What modifiers can a permitted subclass of a sealed type use?
Exactly one of:
final, orsealed, ornon-sealed.
Q4. How do sealed types help with pattern matching?
They allow the compiler to check exhaustiveness in switch over the sealed type, enabling safer pattern matching without needing a default branch.
Q5. Can sealed types be extended in other modules?
No — all permitted subclasses must be in the same module or, without modules, the same package.
Q6. How do sealed hierarchies compare to enums?
Enums model a fixed set of constant values of a single type; sealed hierarchies model a closed set of subtypes, often with different shapes and behaviors.
Q7. What is non-sealed?
A modifier that re-opens the hierarchy at that subclass, allowing it to be extended freely.
17. Summary
Sealed classes and interfaces:
- Let you define closed, explicit hierarchies.
- Greatly improve safety, clarity, and maintainability.
- Work beautifully with:
- records
- pattern matching for
instanceof - pattern matching for
switch - record patterns
- Are standardized from Java 17, and are a cornerstone of modern Java design, especially where algebraic data types or protocol-like hierarchies are needed.
This guide gives you the conceptual model, syntax, rules, examples, limitations, and interview-ready answers for sealed types in Java.