Algebraic data types
“Algebraic” sounds intimidating; the idea is that types have a size you can compute by multiplication and addition. A product type bundles fields — a record is a product: a CitizenFacts contains a citizen ID and a residency flag and an income, so its size is the product of its fields’ sizes. A sum type is a choice — a value is one alternative or another, so its size is the sum of the alternatives’ sizes. Java 21 finally expresses both natively: records for products, sealed interfaces for sums.
The eligibility outcome, as a sum type
An eligibility decision is exactly one of three things — never two, never none:
public sealed interface EligibilityOutcome {
record Eligible(String schemeCode, String citizenId) implements EligibilityOutcome {}
record Ineligible(String schemeCode, String citizenId, List<String> reasons) implements EligibilityOutcome { public Ineligible { reasons = List.copyOf(reasons); } }
record ManualReview(String schemeCode, String citizenId, String reason) implements EligibilityOutcome {}}Two details do real work. Ineligible carries a list of reasons, not one string — a citizen can fail on income and residency at once, and the type should admit that. ManualReview is a first-class alternative, not an exception: a government workflow that cannot decide must still produce a value.
Exhaustiveness for free
Because the interface is sealed, switch knows the complete set of cases:
String message = switch (outcome) { case Eligible eligible -> "Eligible for " + eligible.schemeCode(); case Ineligible denied -> "Ineligible: " + String.join(", ", denied.reasons()); case ManualReview review -> "Manual review required: " + review.reason();};No default branch, and the compiler enforces that: add a fourth outcome — say ConditionallyEligible — and every switch that forgot it fails to compile. That is the payoff of a sum type versus the older alternatives: a boolean plus a nullable reason string lets illegal states exist (eligible=false, reason=null); an enum cannot carry per-case payloads.
Why this matters before Option and Result
Option, Result, and Validated in the next four chapters are just sum types with a generic payload: Some | None, Success | Failure, Valid | Invalid. Once you are comfortable reading EligibilityOutcome as “a value that is one of these shapes,” the library types are the same idea applied to absence, failure, and validation — with pattern matching waiting at the boundary to turn them back into responses.