A sealed interface limits its direct implementations. A pattern switch can then handle the permitted types as an exhaustive set at compile time.
Java sealed hierarchies: switch over permitted request kinds
Operational contract
The sample permits two nested record request kinds and uses a switch expression without a default. If a third permitted kind is added, recompiling this method requires an explicit branch. Both records validate their own values, so each case can work with a constructed domain request. Passing null is not covered by the two type cases and throws at runtime; callers should reject null at intake. Sealing controls direct Java implementations in the same module or package rules; it is not a security boundary for untrusted serialized data.
Failure case
An invoicing service handles a ChargeRequest of 2,300 cents or a RefundRequest of 47 cents. If the model later adds a CreditRequest, the switch must be revisited. A fallback that returns zero for every unknown request would conceal a missing billing rule.
Java code
public class BillingRequestRouter {
public sealed interface Request permits ChargeRequest, RefundRequest { }
public record ChargeRequest(long cents) implements Request {
public ChargeRequest { if (cents < 0) throw new IllegalArgumentException("Negative charge"); }
}
public record RefundRequest(long cents) implements Request {
public RefundRequest { if (cents < 0) throw new IllegalArgumentException("Negative refund"); }
}
public static long signedCents(Request request) {
return switch (request) {
case ChargeRequest charge -> charge.cents();
case RefundRequest refund -> Math.negateExact(refund.cents());
};
}
}Performance and ownership cost
Type dispatch and checked negation are O(1) work with O(1) extra space. Exhaustiveness reduces omitted-source-branch risk; it does not replace input limits, authorization, or a durable transaction.
Common Mistakes
- Do not add a broad default merely to suppress a missing case.
- Do not treat sealed types as validation of wire-format input.
- Do not forget the null boundary when callers can pass null.
Connected lessons
- Java sealed types: restrict a domain’s direct variants
- Java 21 pattern switch: exhaustive branches and null policy
- Java instanceof patterns: keep a narrowed value inside its valid scope
- Java interface defaults: resolve a two-parent method conflict
- Java compact record constructors: normalize before field assignment
- Java switch expressions: return one value from every state
- Java generics and language contracts quiz
- Advanced Java
