Series overview
Part 4 of 2020% complete
2026-04-04•15 min read

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:

scheme/EligibilityOutcome.java
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 {}
}

EligibilityOutcome

Eligible

Ineligible

ManualReview

EligibilityOutcome

Eligible

Ineligible

ManualReview

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:

OutcomeRenderer.java
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.

JavaFunctional Programming

Type to search the site.

↑↓ navigate⏎ openPowered by Pagefind