switch Statement
When you need to compare a single variable against a list of specific values, switch is often cleaner and more readable than a long chain of else if blocks. Java has evolved the switch statement significantly over the years — from its classic C-style roots to the modern expression form introduced in Java 14.
The Classic switch Statement
The traditional switch evaluates an expression and jumps to the matching case label. If none match, it falls through to default.
int day = 3;
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
case 4:
System.out.println("Thursday");
break;
case 5:
System.out.println("Friday");
break;
default:
System.out.println("Weekend");
}
Output:
Wednesday
Note: The
breakat the end of each case is mandatory if you want to stop after a match. Without it, Java falls through to the next case — intentionally or not. We’ll cover fall-through shortly.
What Types Can You Switch On?
Java’s switch is more restrictive than if-else — the selector expression must be one of:
| Allowed Types | Since |
|---|---|
byte, short, int, char | Java 1.0 |
Byte, Short, Integer, Character (wrapper types) | Java 5 |
String | Java 7 |
enum constants | Java 5 |
Pattern types (Object, interfaces, etc.) | Java 21 (preview → final) |
Warning: You cannot switch on
long,float,double, orbooleanin the classic form. Attempting to do so causes a compile-time error.
The default Case
default is the catch-all. It runs when no case value matches the expression. It’s optional, but omitting it can cause silent no-ops that are hard to debug.
String fruit = "Mango";
switch (fruit) {
case "Apple":
System.out.println("Apple selected");
break;
case "Banana":
System.out.println("Banana selected");
break;
default:
System.out.println("Unknown fruit: " + fruit);
}
Output:
Unknown fruit: Mango
Tip: Always include a
defaultcase, even if it just logs a warning or throws an exception. It makes your switch robust against unexpected input.
The default case doesn’t have to be last — it can appear anywhere in the block, though putting it last is the conventional style.
Fall-Through Behavior
Fall-through is one of switch’s most misunderstood features. When you omit break, execution continues into the next case — even if that case’s label doesn’t match.
int level = 2;
switch (level) {
case 1:
System.out.println("Level 1");
case 2:
System.out.println("Level 2"); // matched here
case 3:
System.out.println("Level 3"); // falls through!
break;
case 4:
System.out.println("Level 4");
}
Output:
Level 2
Level 3
This is a common source of bugs. However, fall-through can be intentional — the classic use case is grouping multiple values together:
int month = 4; // April
int year = 2024;
int daysInMonth;
switch (month) {
case 1: case 3: case 5:
case 7: case 8: case 10: case 12:
daysInMonth = 31;
break;
case 4: case 6: case 9: case 11:
daysInMonth = 30;
break;
case 2:
daysInMonth = (year % 4 == 0) ? 29 : 28;
break;
default:
throw new IllegalArgumentException("Invalid month: " + month);
}
System.out.println("Days in month: " + daysInMonth);
Output:
Days in month: 30
switch with String
Since Java 7, you can switch on String values directly — a huge quality-of-life improvement.
String command = "start";
switch (command) {
case "start":
System.out.println("Starting the engine...");
break;
case "stop":
System.out.println("Stopping the engine...");
break;
case "pause":
System.out.println("Pausing...");
break;
default:
System.out.println("Unknown command: " + command);
}
Output:
Starting the engine...
Warning: String switching is case-sensitive —
"Start"will not matchcase "start". Also, passingnullas the selector will throw aNullPointerExceptionat runtime.
switch with enum
Switching on an enum type is clean and type-safe. The JVM can generate an efficient jump table for enum ordinals.
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
Season current = Season.AUTUMN;
switch (current) {
case SPRING:
System.out.println("Time to plant!");
break;
case SUMMER:
System.out.println("Beach season.");
break;
case AUTUMN:
System.out.println("Leaves are falling.");
break;
case WINTER:
System.out.println("Bundle up!");
break;
}
Output:
Leaves are falling.
Note that inside a switch on an enum, you write the constant name alone (AUTUMN), not the fully qualified Season.AUTUMN.
Modern switch Expression (Java 14+)
Java 14 made switch expressions a standard feature (JEP 361). The new -> (arrow) syntax eliminates fall-through entirely and lets switch produce a value.
int day = 3;
String dayName = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
case 4 -> "Thursday";
case 5 -> "Friday";
case 6 -> "Saturday";
case 7 -> "Sunday";
default -> throw new IllegalArgumentException("Invalid day: " + day);
};
System.out.println(dayName);
Output:
Wednesday
Key differences from the classic form:
| Feature | Classic switch | Switch Expression (14+) |
|---|---|---|
| Returns a value | No | Yes |
| Fall-through | Yes (without break) | No — each arm is isolated |
| Syntax | case X: + break | case X -> |
| Multiple labels per arm | case 1: case 2: | case 1, 2 -> |
| Exhaustiveness check | No | Yes (compiler enforces it) |
Multi-label Arms
Group multiple values into a single arm with a comma:
int month = 6;
int daysInMonth = switch (month) {
case 1, 3, 5, 7, 8, 10, 12 -> 31;
case 4, 6, 9, 11 -> 30;
case 2 -> 28; // simplified; ignoring leap years
default -> throw new IllegalArgumentException("Bad month: " + month);
};
System.out.println("Days: " + daysInMonth);
Output:
Days: 30
yield for Multi-Statement Arms
When an arm needs more than a single expression, use yield to return the value:
int score = 85;
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
default -> {
System.out.println("Score below 60 — needs improvement.");
yield "F";
}
};
System.out.println("Grade: " + grade);
Output:
Grade: B
Tip:
yieldis only valid inside switch expression blocks ({}). You cannot usereturnto produce a switch expression value;returnwould return from the entire method.
Pattern Matching in switch (Java 21)
Java 21 finalized pattern matching for switch (JEP 441), letting you match on type and deconstruct objects in a single step:
Object obj = 42;
String result = switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
case null -> "null value";
default -> "Something else: " + obj;
};
System.out.println(result);
Output:
Integer: 42
You can also add a guard condition with when:
Object value = -7;
String description = switch (value) {
case Integer i when i > 0 -> "Positive integer";
case Integer i when i < 0 -> "Negative integer";
case Integer i -> "Zero";
default -> "Not an integer";
};
System.out.println(description);
Output:
Negative integer
This combines cleanly with sealed classes — the compiler can verify exhaustiveness when the sealed hierarchy is known at compile time.
Under the Hood
tableswitch vs. lookupswitch
The Java compiler emits one of two bytecode instructions depending on how dense your case values are:
tableswitch— used when case values are contiguous (or nearly so). The JVM builds an array-like jump table indexed by the selector value. This is an O(1) operation — extremely fast.lookupswitch— used when case values are sparse. The JVM stores sorted key-offset pairs and performs a binary search. This is O(log n) — still very fast, but slightly more work than tableswitch.
You can inspect which instruction your code generates with the javap tool:
javap -c SwitchExample.class
String switch internals
Switching on String is implemented using hashCode() and equals(). The compiler first switches on hashCode() (using a tableswitch or lookupswitch), then confirms identity with equals() to handle hash collisions. This means String switch is O(1) for the hash lookup plus O(k) for string comparison, where k is the string length.
Switch expressions and exhaustiveness
With switch expressions the compiler enforces that all possible values are covered. For enum types, every constant must appear (or default must be present). For sealed class hierarchies, every permitted subtype must be handled. This compile-time guarantee prevents a whole class of runtime bugs.
Note: The JIT compiler can inline and optimize switch expressions aggressively because their control flow is simpler — no fall-through means cleaner data flow analysis.
Classic vs. Modern: Which Should You Use?
If you are on Java 14+, prefer switch expressions for most new code:
- No accidental fall-through
- Forces you to handle all cases
- Can be used as part of an expression (assign, return, pass as argument)
- Multi-label
case 1, 2, 3 ->syntax is cleaner
Use the classic switch statement when you intentionally need fall-through behavior or when your codebase must support older Java versions.
For full details on the expression form, see Switch Expressions. For type-based matching, see Pattern Matching.
Related Topics
- if-else Statement — the other main decision-making tool; better when conditions are complex boolean expressions rather than equality checks.
- Switch Expressions — the modern Java 14+ form of switch that returns values, eliminates fall-through, and enforces exhaustiveness.
- Pattern Matching — Java 16–21 enhancements that let switch match on types and deconstruct objects.
- Enums — switch is at its most powerful and type-safe when combined with enum types.
- Sealed Classes — pair with switch expressions for exhaustive, compiler-verified type hierarchies.
- Control Statements — the full overview of every decision, looping, and jump statement in Java.