Switch Expressions
Switch expressions, finalized in Java 14 (after preview in Java 12–13), let a switch produce a value directly as an expression, using a new arrow (case L ->) syntax that eliminates fall-through by default, supports multiple case labels in a single case, and is checked by the compiler for exhaustiveness when switching over an enum or (later) a sealed type — addressing fall-through bugs and the awkwardness of assigning a switch's result to a variable that have affected the traditional colon-based switch statement since Java 1.0. This entry covers exactly what was wrong with the old switch statement, the new arrow syntax and multi-label cases, the yield keyword for multi-statement case blocks, and exhaustiveness checking and why it matters for enums and sealed types.
Motivation — Fall-Through Bugs and the Statement/Expression Gap
// ── Old switch statement — fall-through risk, separate variable assignment
String dayTypeOld;
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
dayTypeOld = "Weekday";
break; // forgetting this causes silent fall-through into next case!
case SATURDAY:
case SUNDAY:
dayTypeOld = "Weekend";
break;
default:
throw new IllegalArgumentException("Unknown day: " + day);
}
// ── Java 14+ — switch expression, arrow syntax, no fall-through possible
String dayType = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday"; // multiple labels, one arm
case SATURDAY, SUNDAY -> "Weekend";
// no "break" needed or even allowed in arrow form — each arm is independent
};
// Directly assignable as an expression — no pre-declared mutable variable needed,
// and the entire fall-through bug category is structurally impossible hereArrow Syntax, Multi-Statement Blocks, and yield
// ── Single expression per arm — most common form ───────────────────────
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
// ── Block form with yield — for multi-statement case logic ─────────────
int score = switch (grade) {
case 'A' -> 4;
case 'B' -> {
System.out.println("Good job"); // multiple statements allowed in block form
int bonus = computeBonus();
yield 3 + bonus; // "yield" produces the value — "return" would
// exit the enclosing METHOD, not just the switch
}
case 'C' -> 2;
default -> {
System.out.println("Needs improvement");
yield 0;
}
};
// ── "yield" is contextual — still a legal identifier elsewhere ─────────
int yield = 5; // perfectly legal outside a switch expression block
System.out.println(yield);
// ── Old colon-style is still usable inside switch expressions (fall-through preserved)
int result = switch (input) {
case 1:
case 2:
yield 100; // colon-style uses yield too, but DOES fall through between cases
default:
yield 0;
};
// Cannot mix colon-style and arrow-style cases within the SAME switch expressionExhaustiveness Checking — Enums and Sealed Types
// ── Enum exhaustiveness — compiler-verified, no default needed ─────────
enum TrafficLight { RED, YELLOW, GREEN }
String action = switch (light) {
case RED -> "Stop";
case YELLOW -> "Caution";
case GREEN -> "Go";
// no default needed — compiler verifies all 3 enum constants are covered
};
// If TrafficLight later gains a new constant, e.g. FLASHING_RED, this switch
// expression FAILS TO COMPILE until updated — caught at build time, not runtime:
// error: the switch expression does not cover all possible input values
// ── Non-enum, non-sealed selector — default is mandatory ───────────────
int code = 404;
String meaning = switch (code) {
case 200 -> "OK";
case 404 -> "Not Found";
case 500 -> "Server Error";
default -> "Unknown"; // REQUIRED — compiler cannot verify exhaustiveness over all ints
};
// Removing "default" here is a COMPILE ERROR:
// "the switch expression does not cover all possible input values"
// ── Sealed type exhaustiveness — same compiler guarantee, broader scope ─
sealed interface Shape permits Circle, Square, Triangle {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
record Triangle(double base, double height) implements Shape {}
double area = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square s -> s.side() * s.side();
case Triangle t -> 0.5 * t.base() * t.height();
// no default needed — Circle/Square/Triangle are the sealed interface's
// COMPLETE permitted set, verified exhaustive by the compiler
};
// Adding a new permitted subtype to Shape without updating this switch
// fails to compile — the same fail-fast safety enum exhaustiveness provides