Appearance
Java 14 Switch Expressions Deep Dive
Java 14 brought some of the most practical improvements to everyday Java code that developers had been asking for since forever. The switch statement got a complete overhaul, solving real problems that had caused bugs in production for decades. This chapter walks through each problem of the old switch and how Java 14 (where switch expressions became standard) fixes it.
The Old Switch Statement and Its Problems
Before you can appreciate the improvements, you need to understand what was wrong with the original switch. Let us use a simple example throughout this chapter. Imagine you have an enum representing days of the week:
java
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}You want to find out how many characters each day name has. Here is how you would write that with the classic switch statement:
java
Day day = Day.MONDAY;
int count = 0;
switch (day) {
case MONDAY:
System.out.println(6); // MONDAY has 6 characters
break;
case FRIDAY:
System.out.println(6); // FRIDAY has 6 characters
break;
case SUNDAY:
System.out.println(6); // SUNDAY has 6 characters
break;
case TUESDAY:
System.out.println(7); // TUESDAY has 7 characters
break;
case THURSDAY:
case SATURDAY:
System.out.println(8); // both have 8 characters
break;
case WEDNESDAY:
System.out.println(9); // WEDNESDAY has 9 characters
break;
}This works, but it has several serious problems that Java 14 fixes. Let us go through each one.
Problem 1: Verbose Case Stacking
Look at MONDAY, FRIDAY, and SUNDAY above. They all produce the same result (6 characters), yet you need a separate line for each case. In the old style, when you want multiple cases to share the same logic, you stack them like this:
java
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;Each case gets its own line. That is three lines just to say "these three days behave the same way." In a real application with many cases, this adds up to a lot of repetitive boilerplate that makes the code harder to read and maintain.
Java 14 fix: You can now group multiple labels on a single case using commas:
java
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);One line. Three days. Clean and readable.
Problem 2: Fall Through by Default
This is the most dangerous problem with the old switch. In Java (inherited from C), when a matching case runs, execution does not automatically stop at the end of that case. It "falls through" into the next case below it unless you explicitly stop it with a break statement.
Here is a bug waiting to happen:
java
Day day = Day.FRIDAY;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6); // this runs because FRIDAY matches
// OOPS: forgot the break!
case TUESDAY:
System.out.println(7); // this ALSO runs because of fall through
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
}Output:
6
7You expected only 6 but you get both 6 and 7. Why? Because there is no break after the first case block. Execution falls straight through into the TUESDAY case and prints 7 as well. Missing a single break causes silent, hard to find bugs.
This is not a theoretical problem. It has caused real bugs in production systems worldwide. Developers who are tired or rushing forget the break, and the code still compiles and runs without any warning.
Java 14 fix: The new arrow syntax (->) eliminates fall through entirely. When you use ->, only the code on the right side of the arrow runs. Nothing falls through. No break needed.
java
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); // stops here
case TUESDAY -> System.out.println(7); // stops here
case THURSDAY, SATURDAY -> System.out.println(8); // stops here
case WEDNESDAY -> System.out.println(9); // stops here
}If FRIDAY matches, only the first line runs. Execution does not continue to the TUESDAY case. The arrow syntax is a promise: run this and only this.
Problem 3: No Way to Return a Value
This is where things get genuinely painful in the old switch. Suppose you do not just want to print the count, you want to store it in a variable and use it later. In the old approach you have to do this:
java
Day day = Day.TUESDAY;
int count = 0; // define variable before the switch
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
count = 6; // manually set the variable
break;
case TUESDAY:
count = 7;
break;
case THURSDAY:
case SATURDAY:
count = 8;
break;
case WEDNESDAY:
count = 9;
break;
}
System.out.println("Character count: " + count);You have to declare count before the switch, set it inside each case, and then use it after. The variable and the logic are split across different parts of the code. This pattern also makes count technically mutable even though you only want to set it once, and it opens the door to bugs where a case forgets to set the variable.
Java 14 fix: Switch can now be used as an expression. An expression is something that produces a value, like 1 + 5 or a method call that returns something. You can now assign the result of a switch directly to a variable:
java
Day day = Day.TUESDAY;
int count = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6; // returns 6 directly
case TUESDAY -> 7; // returns 7 directly
case THURSDAY, SATURDAY -> 8; // returns 8 directly
case WEDNESDAY -> 9; // returns 9 directly
};
System.out.println("Character count: " + count);Notice the semicolon at the end of the closing brace. Because the entire switch is an expression being assigned to count, it needs that semicolon to end the assignment statement, just like int count = someMethod(); needs one.
With single statement cases using the arrow syntax, the value to the right of the arrow is automatically returned. You write -> 6 and 6 becomes the result of the switch for that case.
The yield Keyword: Returning Values from Blocks
What happens when a case needs more than one line of code? You cannot just write multiple statements after an arrow without putting them in a block:
java
int count = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> {
// Now you have a block with multiple statements
System.out.println("Processing a 6-letter day...");
// How do you return 6 from inside this block?
yield 6; // <-- this is how
}
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};The yield keyword is how you return a value from inside a block in a switch expression. Think of it as return but specifically for switch expressions. When the JVM hits yield 6, it takes that value and delivers it as the result of the entire switch expression.
You can also handle special situations inside a block. For example, if Sunday is a holiday and you do not want to return a normal value:
java
int count = switch (day) {
case MONDAY, FRIDAY -> {
System.out.println("Processing weekday...");
yield 6;
}
case SUNDAY -> {
System.out.println("Checking Sunday...");
throw new IllegalArgumentException("Sunday is a holiday, no work!");
// no yield needed: the block ends by throwing
}
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};The rule is simple: when you use a block {} in a switch expression case, you must either yield a value or throw an exception. You cannot just let the block finish without producing a result, because the switch expression promised to deliver a value.
When do you use yield and when do you not? Here is the pattern:
| Situation | What to do |
|---|---|
Single statement with arrow (->) | Value is returned automatically, no yield needed |
Multiple statements with arrow and block (-> {}) | Must use yield to return a value |
Old colon style (:) as an expression | Must use yield to return a value |
Problem 4: No Exhaustiveness Check
In the old switch statement, the compiler never forced you to handle every possible value. This compiles perfectly:
java
Day day = Day.FRIDAY;
int count = 0;
switch (day) {
case MONDAY:
count = 6;
break;
case TUESDAY:
count = 7;
break;
// FRIDAY, WEDNESDAY, THURSDAY, SATURDAY, SUNDAY all missing!
}
System.out.println(count); // prints 0 because FRIDAY was not handledThe output is 0. The variable stays at its default value because no case matched FRIDAY. The compiler said nothing. This is another source of silent bugs.
Java 14 fix: When you use switch as an expression (assigning it to a variable or using it inline), the compiler forces you to cover every possible input value. This is called the exhaustiveness requirement.
java
int count = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
// WEDNESDAY is missing!
};
// COMPILE ERROR: switch expression does not cover all possible input valuesThe compiler refuses to compile this. You must either add every enum value or add a default case:
java
// Option 1: cover every enum value explicitly
int count = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
// Option 2: use default as a catch all
int count = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
default -> 9; // covers WEDNESDAY and anything else
};
// Option 3: throw an exception for unrecognized values
int count = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
default -> throw new IllegalArgumentException("Unknown day: " + day);
};This exhaustiveness check only applies when you use switch as an expression. If you use it as a plain statement (not assigning the result anywhere), the compiler does not enforce coverage. The moment you want a value out of the switch, the compiler becomes strict.
Problem 5: Shared Scope Across Cases
In the old switch, the entire switch block is one shared scope. Every variable declared in any case is visible in all other cases. This causes unexpected compilation errors:
java
switch (day) {
case MONDAY:
String val = "Monday";
System.out.println(val);
break;
case TUESDAY:
String val = "Tuesday"; // COMPILE ERROR: val is already defined in scope
System.out.println(val);
break;
}Even though MONDAY and TUESDAY are logically separate, they share the same scope. val is declared twice in the same scope, which is illegal. The workaround in old Java is to wrap each case in its own explicit block:
java
switch (day) {
case MONDAY: {
String val = "Monday"; // now in its own block scope
System.out.println(val);
break;
}
case TUESDAY: {
String val = "Tuesday"; // separate block, separate scope
System.out.println(val);
break;
}
}Java 14 fix: With arrow syntax, each case is automatically treated as its own independent scope. You do not need to add blocks manually when you only have a single statement. When you do need multiple statements, you use a block and that block is naturally isolated:
java
int count = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> {
String val = "short name"; // only visible inside this block
System.out.println(val);
yield 6;
}
case TUESDAY -> {
String val = "medium name"; // completely separate from the block above
System.out.println(val);
yield 7;
}
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};No conflict. Each block is its own world.
Switch Statement vs Switch Expression: The Key Difference
This distinction is easy to miss, so here it is side by side.
A switch statement does something. It performs actions but does not produce a value that you capture.
A switch expression produces something. It computes and returns a value that you assign or use inline.
Here is a switch statement using the new arrow syntax:
java
// switch STATEMENT: just doing something, not capturing a value
switch (day) {
case MONDAY -> System.out.println("Start of the work week");
case FRIDAY -> System.out.println("End of the work week");
default -> System.out.println("Middle of the week");
}Here is a switch expression:
java
// switch EXPRESSION: producing a value
String message = switch (day) {
case MONDAY -> "Start of the work week";
case FRIDAY -> "End of the work week";
default -> "Middle of the week";
};The structural difference is that in the expression form, the switch sits on the right side of an assignment (or is used inline in another expression), and each case returns a value rather than performing a void action.
The exhaustiveness requirement only kicks in for switch expressions. The compiler needs to know that no matter what value comes in, the expression will always produce a result. A switch statement can be silent for unmatched cases, but a switch expression cannot be.
Mixing Old Colon Style with New Features
Java 14 did not remove the old colon style. You can still use case MONDAY: with a colon. You can even mix new features (like comma separated labels) with the old colon style:
java
String type = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY:
yield "Weekday"; // must use yield when using colon style as expression
case SATURDAY, SUNDAY:
yield "Weekend";
};The critical rule to remember: colon always means fall through is possible. When you use a colon, the old fall through behavior is still there. You must either use break (for statement use) or yield (for expression use) to stop execution from falling into the next case.
Arrow (->) means no fall through, ever. You never need break with arrows.
Here is a simple mental model:
- Arrow (
->): No fall through. No break needed. Useyieldonly if you have a block with multiple statements and need to return a value. - Colon (
:): Fall through possible. Usebreakto stop it in statement mode. Useyieldto return a value and stop in expression mode.
Complete Working Example
Here is a complete program that demonstrates everything:
java
public class SwitchDemo {
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
public static void main(String[] args) {
Day day = Day.WEDNESDAY;
// Switch expression with arrow syntax (most modern approach)
int charCount = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
System.out.println("Character count: " + charCount);
// Switch expression with blocks and yield
String description = switch (day) {
case MONDAY -> {
System.out.println("Processing Monday...");
yield "Start of work week"; // required inside a block
}
case FRIDAY -> {
System.out.println("Processing Friday...");
yield "End of work week";
}
case SATURDAY, SUNDAY -> "Weekend";
default -> "Middle of work week";
};
System.out.println(description);
// Switch statement with arrow syntax (no value returned)
switch (day) {
case MONDAY -> System.out.println("Monday!");
case FRIDAY -> System.out.println("Friday!");
default -> System.out.println("Another day.");
}
}
}Check yourself
- In the old switch, what happens if you forget
breakin a matching case? - Rewrite
case MONDAY: case FRIDAY: case SUNDAY:in Java 14 style. - Does
->ever fall through? - When must you use
yield? - A switch expression over an enum lists only 6 of the 7 constants and has no
default. What happens? - Can a case in a switch expression throw instead of returning a value?
- Why does declaring
String valin two old-style cases fail, and why does it work with->blocks? - Is exhaustiveness required for a switch statement written with arrows?
Answers
- Execution falls through into the next case's code.
case MONDAY, FRIDAY, SUNDAY ->(or with a colon).- No. Only the code right of the arrow runs.
- Inside a
{}block of a switch expression, and in colon-style cases of a switch expression. - Compile error: the switch expression does not cover all possible input values.
- Yes, throwing completes the case.
- Old-style cases share one scope, so it is a duplicate declaration. Each arrow block is its own scope.
- No (for enums/strings/ints as in Java 14), only switch expressions must be exhaustive.