Modern Java Switch Expressions
Use modern switch expressions in Java with arrow labels, yield, exhaustiveness, and pattern matching.
Modern Java Switch Expressions
The traditional switch statement has been part of Java since version 1.0, but it carried a lot of baggage: fall-through bugs, repetitive break statements, and no way to produce a value. Switch expressions, finalized in Java 14, fix all of that. They turn switch from a clumsy control-flow statement into a concise, value-producing expression.
This chapter walks through the modern switch: arrow labels, the yield keyword, multi-label cases, exhaustiveness checking, and exactly how the new form differs from the statement you may already know.
From Statement to Expression
The classic switch statement runs side effects and relies on break to stop fall-through. Forget a break and execution silently tumbles into the next case—a notorious source of bugs.
// Traditional switch statement (error-prone)
String kind;
switch (day) {
case SATURDAY:
case SUNDAY:
kind = "weekend";
break; // forget this and you fall through
default:
kind = "weekday";
}A switch expression collapses that into a single assignment. The arrow (->) form never falls through, so no break is needed.
// Modern switch expression
String kind = switch (day) {
case SATURDAY, SUNDAY -> "weekend";
default -> "weekday";
};The whole switch now evaluates to a value, which you can assign, return, or pass as an argument directly.
Arrow Labels and Multi-Label Cases
The arrow label case L -> associates one label (or several, comma-separated) with a single action. Only the matching branch runs—there is no fall-through to worry about.
int numLetters = switch (month) {
case JANUARY, JUNE, JULY -> 4;
case FEBRUARY, MARCH, APRIL, MAY -> 5;
case SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> switchOnLength(month);
case AUGUST -> 6;
};Grouping labels with commas replaces the old trick of stacking empty case lines to share a body, and it reads far more clearly.
| Feature | Traditional switch (case L:) | Modern switch (case L ->) |
|---|---|---|
| Fall-through | Yes, unless you break | No, each branch is isolated |
| Produces a value | No | Yes (it is an expression) |
| Multiple labels | Stacked empty case lines | Comma-separated on one line |
| Scope of variables | Shared across the whole block | Local to each branch block |
Blocks and the yield Keyword
When a branch needs more than a single expression, use a block { ... } and return its value with yield. The yield keyword is to a switch expression what return is to a method: it supplies the value the branch produces.
int gradePoints = switch (grade) {
case 'A' -> 4;
case 'B' -> 3;
default -> {
log("Unknown grade: " + grade);
yield 0; // the value this branch evaluates to
}
};You can still use colon labels with yield if you prefer the old syntax, but the arrow form is the idiomatic modern choice and avoids accidental fall-through entirely.
Exhaustiveness and default
A switch expression must be exhaustive: every possible input must be handled, because the expression has to produce a value no matter what. For most types you satisfy this with a default branch. For an enum, the compiler can verify exhaustiveness directly—if you cover every constant, default becomes optional.
// No default needed: all enum constants are covered,
// so the compiler knows the switch is exhaustive.
boolean isWeekend = switch (day) {
case SATURDAY, SUNDAY -> true;
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
};If you omit a constant and there is no default, the code will not compile. This compile-time safety net is one of the biggest practical wins over the old statement, which would silently leave a variable unassigned.
A Complete Worked Example
The program below puts the pieces together: arrow labels with multi-label cases, a yield block for a computed branch, exhaustive enum coverage, and a switch expression assigned straight to a variable.
What to take from the run:
kind()returns the result of a switch expression directly—MONDAYandFRIDAYprintweekday,SATURDAYprintsweekend, all without a singlebreak.- The two arrow branches in
kind()cover all seven enum constants, so the switch is exhaustive and needs nodefault. - In
letterGrade(), scores 95, 83, 71, and 64 map cleanly through arrow labels to 4, 3, 2, and 1 grade points. - The score 42 hits the
defaultblock, which first prints(failing score 42)via the side-effect line and thenyields 0—showing how a block branch can do work before producing its value. - The final
switchassignsTWOstraight into thelabelvariable, proving a switch expression is a value you can store, not just control flow.
Practice
In a modern switch expression, which keyword produces a value from inside a block (the { ... } form of a branch)?