Java 17 introduced sealed classes, which restrict the allowed subtypes of an interface or base class to an explicit list. According to Neil Madden’s technical blog, this feature enables exhaustive pattern matching—the compiler can verify that all possible cases are handled without requiring a default clause.

For example, a sealed interface with two record implementations can be used in a switch expression that covers both types. The compiler confirms completeness at build time, preventing missed cases. Madden compares this to algebraic datatypes in functional languages like Haskell or ML, which offer similar type safety.
However, sealed classes introduce a significant breaking change scenario. Adding a new subtype to a sealed class forces any code that pattern-matches against it to be updated. If consumers are not recompiled when the sealed type changes, the compiler cannot detect the missing case. Instead, a MatchException is thrown at runtime.
Madden illustrates this problem with a concrete example: if TypeC is added to an existing sealed interface and the consumer code (Main) is not recompiled, running the code with the new case triggers a runtime exception rather than a compile-time error. The Javadoc for sealed classes acknowledges this limitation with separate compilation, noting that exhaustiveness checking only works when everything is recompiled together.
Madden concludes that sealed classes are most valuable for internal implementation within a component, where all code is typically recompiled as a unit. When exposed in a public API—such as a library interface—sealed types may introduce more problems than they solve, creating new ways to break code that are often only detectable at runtime. He notes observing this exact mistake in Java’s preview support for PEM-encoded cryptographic objects, which bakes a sealed interface into a public API while recommending clients pattern-match against it, predicting “a very high chance of breakage if they ever want to add a new case.”
Key facts
- Java 17 sealed classes restrict allowed subtypes to an explicit list, enabling exhaustive pattern matching
- Exhaustive pattern matching allows compile-time verification that all cases are handled without a default clause
- Adding new subtypes to a sealed class is a breaking change requiring recompilation of all consumers
- If consumers are not recompiled, missing cases result in runtime MatchException rather than compile-time errors
- Sealed classes are most useful within component implementations where all code recompiles together, less useful in public APIs
