Pimp My IDE / type-change dispatch
Back to garage
Java switch / separate compilation / 18 Oct 2026

The compiler cannot inspect a build you skipped.

Exhaustive pattern matching turns a new type case into a useful fault. The fault appears only when every consumer returns to the compiler.

Treat a closed type as a rebuild contract. Add a case, rebuild every consumer, fix every broken match, then run the new value through the route.
SEALED TYPE INPUTSWITCH HOUSING
AMATCHED
BMATCHED
CNEW CASE

Exhaustiveness is a change alarm.

Java sealed classes name the permitted subtypes of a class or interface. Pattern matching for switch became final in Java 21. The compiler can check that a pattern switch covers the selector type.[1]

That check is useful because it turns a missing case into a build failure. A catch-all branch weakens the alarm. The code still compiles when the type grows, but the new value falls into a generic path that may hide the change from review.

A closed type does not remove change. It chooses where change should make noise.

Separate compilation opens a side door.

Neil Madden demonstrates the boundary with a sealed Java interface. A consumer compiles an exhaustive switch for TypeA and TypeB. The producer then adds TypeC. If the consumer is not recompiled, sending the new value through the old switch can throw MatchException at runtime.[2]

The original compile was correct for the type it saw. The stale binary never saw the new permitted subtype. Exhaustiveness therefore depends on type closure and build closure. Your release route must bring every affected consumer back through compilation.

A rebuild is necessary, not sufficient.

Recompiling the consumer exposes the missing branch. Adding the branch restores compilation. Neither step proves that the branch does the right work. Send a real TypeC value through the updated path and inspect the result.

Keep these records separate:

  1. The producer revision that adds the subtype.
  2. The consumer revision compiled against that producer.
  3. The compiler output before the missing branch is fixed.
  4. The runtime result for the new value after the branch is added.

Other languages use different hardware.

TypeScript can narrow a discriminated union to never after every member is handled. The handbook shows two common checks: assign the remaining value to never, or pass it to an assertNever function.[3] The check still depends on updated type declarations and a compiler run.

Rust requires match expressions to cover every possible case of the matched value. The Rust Book shows the compiler rejecting a missing Option<T> case and explains that a wildcard pattern can satisfy the requirement.[4] That wildcard has the same review cost as a Java default: future cases can pass without naming their behavior.

Do not flatten these languages into one rule. Java class evolution, TypeScript's erased runtime, and Rust enum compilation have different package and runtime boundaries. The reusable habit is narrower. Keep the finite type, the matching code, the build, and the new-case test in one change packet.

Ship the alarm with the API.

PRODUCER_REVISION=[REQUIRED]
CONSUMER_REVISION=[REQUIRED]
DECLARED_CASES=[A, B, C]
CATCH_ALL_BRANCH=[ABSENT OR JUSTIFIED]
REBUILD_COMMAND=[REQUIRED]
MISSING_CASE_DIAGNOSTIC=[SAVED]
NEW_CASE_FIXTURE=[REQUIRED]
NEW_CASE_RESULT=[REQUIRED]
RELEASE_DECISION=[REQUIRED]

The walkthrough below makes the Java sequence visible. It teaches the expected checkpoints. It does not compile code or prove a release.

Interactive makeover / temporal build rail

Exhaustiveness rebuild breaker

A static checklist hides when the compiler gains authority. This four-detent rail shows the producer change, the stale binary, the rebuild fault, and the repaired route in order.

Move the release key

Choose one stage. Native radio controls keep the sequence keyboard accessible.

Release stage
Stage 1 of 4Teaching walkthrough
Baseline compiled.

The consumer handles every case declared by the producer revision it saw.

sealed interface Result permits TypeA, TypeB {}

return switch (result) {
  case TypeA a -> handleA(a);
  case TypeB b -> handleB(b);
};
Sources read / 18 Oct 2026

Source locker

Language specification

OpenJDK JEP 441

Finalizes pattern matching for switch in Java 21. It states that pattern switch statements must cover all possible input values.

Read JEP 441
Separate compilation demonstration

Neil Madden on sealed Java APIs

Shows a sealed interface growing after a consumer was compiled and records MatchException when the stale switch receives the new case.

Read the article
Cross-language comparison

TypeScript handbook

Documents discriminated unions, control-flow narrowing, the never type, and two patterns for exhaustiveness checking.

Read the handbook
Cross-language comparison

The Rust Programming Language

Explains that match patterns must cover every possibility and shows how wildcard patterns satisfy the compiler.

Read the Rust Book
Discovery record

Hacker News item 50141579

Surfaced the Java article for current discussion. The thread is a discovery route, not the source for Java semantics.

Read the discussion

Sources were inspected on 18 Oct 2026. Product and language behavior claims remain scoped to the linked documentation and demonstration.