Learn how to implement sealed classes and interfaces cleanly in Java without turning a closed hierarchy into a brittle one.
Implementing sealed classes well is mostly about choosing the right closure boundary. The syntax is straightforward. The design question is whether the domain is truly finite enough to justify sealing the hierarchy.
1public sealed interface OrderState
2 permits Draft, Submitted, Fulfilled, Cancelled {}
3
4public record Draft(UUID id) implements OrderState {}
5public record Submitted(UUID id, Instant submittedAt) implements OrderState {}
6public record Fulfilled(UUID id, Instant fulfilledAt) implements OrderState {}
7public record Cancelled(UUID id, String reason) implements OrderState {}
This works well because:
Do not seal a hierarchy merely because the current codebase happens to have four implementations. Seal it when the business concept itself is closed.
That usually means:
If external extension is part of the design, sealing is the wrong move.
Sealed interfaces paired with records are often excellent for lightweight algebraic-style modeling:
1public sealed interface LoginOutcome
2 permits LoginOutcome.Allowed, LoginOutcome.Blocked {
3
4 record Allowed(UUID userId) implements LoginOutcome {}
5 record Blocked(String reason) implements LoginOutcome {}
6}
This model is compact, immutable, and easy to branch on. It is one of the clearest examples of how modern Java can express data-oriented domain variants.
if chains anyway.Implement sealed hierarchies when the domain truly has a bounded set of legal forms. Keep the root type small, the variants meaningful, and the closure boundary tied to business truth rather than convenience.