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 permits clause 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:

  • Shape is sealed.
  • Only Circle, Rectangle, and Square may implement Shape.

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:

  1. Restrict inheritance: you decide which classes belong to the hierarchy.
  2. Make code safer: no unexpected subclasses from other modules/packages.
  3. Enable better pattern matching: compiler can check exhaustiveness in switch on sealed types.
  4. 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 further
  • sealed – further restricted hierarchy
  • non-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:

  1. Each permitted subclass must:
  2. Be in the permits clause of S, or
  3. (In some cases) be in the same compilation unit and automatically discovered (JEP 409 refinement).
  4. Each permitted subclass must:
  5. Directly extend / implement S.
  6. Each permitted subclass must be declared as:
  7. final, sealed, or non-sealed.
  8. Permitted subclasses must be in:
  9. The same module (if using modules), or
  10. 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 {}
  • Shape is 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 {}
  • Other is a permitted subclass of Animal, 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-private sealed, 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 abstract or interface to 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 Shape is sealed and all implementations are covered.
  • If you add a new subtype of Shape, this switch will 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, or
  • sealed, or
  • non-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 permits clause (if used),
  • Fix all exhaustive switch expressions if needed.
  • Remove a permitted subclass → must:
  • Update the permits clause,
  • 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; }
}
  • Expr has 2 branches: Num and Binary.
  • Binary is itself sealed, but Mul is non-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-sealed sparingly: 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, or
  • sealed, or
  • non-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.