Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java sealed types: restrict a domain’s direct variants

Last updated: 28 Sept 20263 min read
tutorial
IntermediateBy AITrove Editorial

A sealed class or interface restricts which types may directly extend or implement it through a declared set of permitted variants.

Java 17+. Use a JDK that supports this release.

Model a closed decision

A settlement is either accepted with an amount or rejected with a reason. Declaring that pair as permitted variants prevents unrelated implementations from entering this particular model without changing its type declaration.

Each permitted direct subtype must state its extension policy: final, sealed, or non-sealed. Records are final, which fits these leaf values. A non-sealed subtype deliberately reopens its branch; it is not another closed leaf.

Permitted types obey locality rules. In a named module they belong to the same module; in the unnamed module they belong to the same package. The sample keeps them together as nested types in one source file.

A closed hierarchy still needs validation

Knowing the set of variants does not prove an accepted amount is nonnegative or a rejection reason is useful. The record constructors validate those rules. Type closure and value validity solve different problems.

The Java 17 program uses instanceof with a binding variable. The Java 21 pattern switch lesson shows exhaustive branching without a catch-all fallback that could conceal a newly added variant.

Working program

Java
public class SettlementVariants {
    sealed interface Settlement permits Accepted, Rejected { }
    record Accepted(long cents) implements Settlement {
        Accepted { if (cents < 0) throw new IllegalArgumentException("Negative amount"); }
    }
    record Rejected(String reason) implements Settlement {
        Rejected {
            if (reason == null || reason.isBlank()) throw new IllegalArgumentException("Reason required");
        }
    }
    static String describe(Settlement settlement) {
        if (settlement instanceof Accepted accepted) return "accepted=" + accepted.cents();
        if (settlement instanceof Rejected rejected) return "rejected=" + rejected.reason();
        throw new IllegalArgumentException("Settlement required");
    }
    public static void main(String[] args) {
        System.out.println(describe(new Accepted(1250)));
        System.out.println(describe(new Rejected("expired")));
    }
}

Output

Output
accepted=1250
rejected=expired

Cost and design choices

The type test selects one of a small fixed number of variants. It does not require scanning a registry. The resulting text allocation depends on its length.

Use an open interface for plugin implementations that independent modules must add. Closing that interface and later adding reflection-based escape routes defeats the ownership boundary.

Common Mistakes

  • Do not forget the extension policy of a permitted subtype.
  • Do not use non-sealed while claiming the entire branch is closed.
  • Do not confuse a permitted type with a validated value.

Connect the contracts

Compare the boundary explained in Open contracts with the assumptions made by this program.

java
sealed-classes
Storage details