LESSON 11 · BEGINNER4 min read

Sealed Hierarchies & Exhaustive Pattern Matching in Java 21

Master modern algebraic data modeling. Restrict inheritance with sealed classes, eliminate visitor patterns, and leverage exhaustive pattern matching for switch.

OpenJDK 21+ (LTS)Free to readModule 3 of 15
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Declare Sealed Classes and Interfaces with explicit 'permits' clauses.
  • Understand final, sealed, and non-sealed subclass modifiers.
  • Write exhaustive Pattern Matching switch expressions with type patterns and record deconstruction.

01Sealed Classes: Restricting Inheritance Hierarchies

Historically, Java only provided two extremes for inheritance: open inheritance (any class can extend, unless restricted by package-private visibility) or complete closure (final classes cannot be extended at all). There was no way to declare: 'Class A can be extended, but ONLY by classes B, C, and D.'

Sealed Classes and Interfaces (sealed interface Shape permits Circle, Rectangle) solve this. By explicitly declaring which subclasses are permitted to extend them, sealed types allow developers to model bounded domain hierarchies (Algebraic Data Types) with mathematical precision.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Subclass Modifiers: final, sealed, and non-sealed

Every permitted subclass of a sealed class must explicitly declare one of three modifiers to define its own inheritance posture:

1. final: The subclass cannot be extended any further (e.g. records are implicitly final). 2. sealed: The subclass continues to restrict inheritance to its own permitted subtypes. 3. non-sealed: The subclass opens itself up for unrestricted inheritance by any external class. This gives architects complete, granular control over the API boundary.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03Exhaustive Pattern Matching for switch (Java 21)

The combination of Sealed Types and Java 21 Pattern Matching for switch revolutionizes business logic. Because the compiler knows every possible subtype permitted by the sealed interface, switch expressions can be statically proven to be Exhaustive.

You no longer need a dummy default: case that throws an exception! If a developer later adds a new permitted subtype (e.g. PayPalPayment), the compiler immediately raises a compile error on every switch expression that has not handled the new variant.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

04Why This Matters in Production

Sealed classes completely eliminate the classic Gang of Four Visitor Pattern, which required hundreds of lines of awkward double-dispatch boilerplate to traverse domain objects.

By pairing sealed hierarchies with pattern-matching switch expressions, domain logic is clean, co-located, and compiler-verified. Adding a new domain variant becomes a safe, guided refactoring where the compiler guides you to every single place in the codebase that needs updating.

05Production Architecture & Engineering Trade-Offs

In large-scale enterprise codebases, rigorous domain modeling relies on enforcing structural invariants at the type level rather than relying on defensive runtime validation. Modern Java leverages sealed types and records to turn runtime type errors into compile-time exhaustiveness guarantees. When designing domain transfer objects or persistent entities, ensuring immutability protects against unintended side effects across concurrent worker threads.

Furthermore, avoiding deep inheritance hierarchies in favor of composition and interface contracts eliminates fragile base class anti-patterns. By encapsulating state mutations within well-defined service boundaries and exposing pure functional transformers, software architects build maintainable, audit-compliant systems that scale smoothly across distributed cloud infrastructure.

LET IT CLICK

The ideas worth keeping.

  • Sealed classes and interfaces restrict inheritance to an explicit permitted set of subtypes.
  • Subclasses must declare final, sealed, or non-sealed to maintain architectural boundaries.
  • Pattern matching switch expressions provide exhaustive, compiler-verified type handling without visitor boilerplate.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Exercise 1: Modeling Result<T, E> Algebraic Type

Implement a sealed interface Result<T, E> permitted to be Success(T data) or Failure(E error) using records.

your_turn_1.java☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.javaJava
public sealed interface Result<T, E> permits Result.Success, Result.Failure {    record Success<T, E>(T data) implements Result<T, E> {}    record Failure<T, E>(E error) implements Result<T, E> {}}

Sealed Result provides a functional error handling type that can be exhaustively matched with pattern matching switch.

02

Exercise 2: Exhaustive Evaluation Switch

Write a function evaluate(Result<String, Exception> res) that extracts data or formats the error message using a switch expression.

your_turn_2.java☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.javaJava
public static String evaluate(Result<String, Exception> res) {    return switch (res) {        case Result.Success(String data) -> "OK: " + data;        case Result.Failure(Exception err) -> "ERR: " + err.getMessage();    };}

Record deconstruction patterns extract component variables directly inside the case label without casting.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01Why does a switch expression over a sealed hierarchy not require a 'default' branch?
02Which three modifiers must any permitted subclass of a sealed class declare?
03What happens if a new permitted subclass is added to a sealed interface in an existing codebase?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Exercise 1: Modeling Result<T, E> Algebraic Type

Implement a sealed interface Result<T, E> permitted to be Success(T data) or Failure(E error) using records.

EXPECTED OUTPUT
Build successful. (Java 15)
[Process exited normally]
Main.javaJava
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.