Definition
Two forms of switch:
- **Switch statement (classic).** Executes statements in matching case blocks. Fall-through by default: without `break`, execution continues into the next case. Does not return a value. - **Switch expression (Java 14+).** Returns a value. Uses `->` arrow syntax. No fall-through. Must be exhaustive (cover all possible inputs or have a default). Use `yield` for multi-statement blocks.
A bank rate card. The switch statement is a clerk who reads the card, finds the matching row, and then keeps reading the next rows unless you tell them to stop (break). The switch expression is a smart card reader that finds the row, returns the rate, and stops automatically.
Understanding the Concepts
How does the classic switch statement work?
String accountType = "SAVINGS";
double rate;
switch (accountType) {
case "SAVINGS":
rate = 4.0;
break; // without break, falls through to next case!
case "CURRENT":
rate = 0.0;
break;
case "FIXED_DEPOSIT":
rate = 7.5;
break;
default:
rate = 0.0;
break;
}
System.out.println("Rate: " + rate);
Fall-through bug (forgetting break):
// BUG: missing break on SAVINGS
switch (accountType) {
case "SAVINGS":
rate = 4.0; // matched here
// no break! falls through
case "CURRENT":
rate = 0.0; // overwrites rate!
break;
}
// rate is 0.0, not 4.0!
How does the switch expression work (Java 14+)?
String accountType = "SAVINGS";
double rate = switch (accountType) {
case "SAVINGS" -> 4.0;
case "CURRENT" -> 0.0;
case "FIXED_DEPOSIT" -> 7.5;
default -> 0.0;
};
System.out.println("Rate: " + rate); // 4.0
No break needed. No fall-through possible. The arrow -> returns the value directly.
How does yield work for multi-statement cases?
double rate = switch (accountType) {
case "SAVINGS" -> 4.0;
case "LOAN" -> {
double base = 8.5;
double margin = calculateMargin(creditScore);
yield base + margin; // yield returns the value from the block
}
default -> 0.0;
};
yield is like return but for switch expression blocks.
What types can switch operate on?
// Classic: byte, short, char, int, String, enum
switch (dayOfWeek) { case MONDAY: ... }
// Switch expression: same types + pattern matching (Java 21+)
var result = switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
case null -> "null";
default -> "Other: " + obj;
};
What does exhaustive mean?
Switch expressions must cover every possible value. The compiler enforces this.
enum AccountType { SAVINGS, CURRENT, LOAN }
// This compiles: all enum values covered
String label = switch (type) {
case SAVINGS -> "Savings";
case CURRENT -> "Current";
case LOAN -> "Loan";
// no default needed: all enum values handled
};
// This does NOT compile: missing LOAN
// String label = switch (type) {
// case SAVINGS -> "Savings";
// case CURRENT -> "Current";
// }; // COMPILE ERROR: not exhaustive
For String and int switches, you need a default because the compiler cannot enumerate all possible values.
What are common mistakes?
- Forgetting break in classic switch. Causes fall-through bugs. Use switch expressions to eliminate this.
- Forgetting default in switch expression on String/int. Must be exhaustive. Add default.
- Null input. Before Java 21, switch on a null String/Integer throws NPE. Java 21+ adds
case null. - Using switch for 2 values. If-else or ternary is simpler for 2 cases.
What should a senior engineer know?
- Switch expressions can assign to final variables.
final var rate = switch(...) {...};is cleaner than declaring + assigning in if-else. - Pattern matching (Java 21+) replaces instanceof chains.
case Account a when a.isActive() -> ...combines type check + guard. - Sealed classes + switch is exhaustive without default: if a sealed interface has 3 permitted subtypes and all are covered, no default needed.
- Performance: switch on int/enum compiles to
tableswitchorlookupswitchbytecodes, which are O(1). Switch on String uses hashCode + equals.
Visual Overview
Classic switch statement: Switch expression (Java 14+):
switch (type) { var rate = switch (type) {
case "SAVINGS": case "SAVINGS" -> 4.0;
rate = 4.0; case "CURRENT" -> 0.0;
break; ← must remember! default -> 0.0;
case "CURRENT": }; ← returns a value
rate = 0.0;
break;
default:
rate = 0.0;
}
Key differences:
Fall-through: statement YES (needs break) expression NO
Returns value: statement NO expression YES
Exhaustive: statement optional default expression REQUIRED
Arrow syntax: statement uses : expression uses ->
Key Takeaway
The classic switch statement executes code blocks and requires break to prevent fall-through. Forgetting break is one of the most common bugs in Java. The switch expression, introduced in Java 14, fixes this by returning a value, using arrow syntax with no fall-through, and requiring exhaustive coverage of all possible inputs.
Use switch expressions when you need to assign a value based on multiple cases. Use yield inside a block when a case needs multiple statements. And use pattern matching (Java 21+) to replace instanceof chains with type-matching cases.
One sentence to remember: switch expression uses arrows, returns a value, has no fall-through, and must be exhaustive – prefer it over the classic switch statement.
---