Topic 2.3
The switch Statement
In one line
A switch statement compares one value against a list of constant case labels and jumps straight to the matching one. In the classic colon form, execution falls through into the following cases until a break, which is both its trick and its most common bug.
Think of it like this
A lift (elevator) panel. You press one button, say 3, and the lift goes straight to floor 3. It doesn't stop at floors 1 and 2 to check. A switch works the same way: it takes one value and jumps directly to the matching case, instead of asking a chain of yes/no questions like an else-if ladder.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Selector
- The value in
switch (...)that is compared against the cases. - Case label
- A
case 3:line that marks where to jump when the selector equals 3. - Compile-time constant
- A value the compiler knows while compiling, such as
7,"red", or astatic final intset to a literal. - Fall-through
- In a colon-style switch, carrying on into the next case's statements because there was no
break. - break
- A statement that jumps out of the switch (or loop) immediately.
- default
- The label that runs when no case matches.
- enum
- A type with a fixed list of named values, like
enum Day { MON, TUE, WED }. Phase 6 covers them fully. - Jump table
- A list of addresses indexed by the case value, so the program can jump to the right case in one step.
Step by step
01The shape of a switch
Write switch, the selector in round brackets, then a block of case labels. Each label is followed by a colon and the statements to run. break leaves the switch.
With day = 3, Java jumps straight to case 3:, prints Wednesday, hits break, and continues after the closing brace.
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;
default:
System.out.println("Some other day");
}
System.out.println("done");02Fall-through: what happens without break
A case label is only an entry point, not a wall. Once Java enters at a label, it runs downward until a break (or the end). Remove the breaks from the example above with day = 2, and you get Tuesday, Wednesday and 'Some other day'.
This design comes from C. It's why break after every case is a habit you must build, and why Java 14 added arrow labels that never fall through (Topic 2.4).
03Fall-through on purpose: grouping cases
Fall-through is useful when several values share one action. Stack the labels with nothing between them: entering at any of them flows down to the shared code.
Since Java 14 you can also write several values in one label, case 6, 7:, even in the colon form. It's clearer than stacking.
int day = 6;
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println("Weekday");
break;
case 6, 7: // Java 14+: several labels in one case
System.out.println("Weekend");
break;
default:
System.out.println("Not a day");
}04What can be switched on, and what labels can be
Selector types (classic switch): char, byte, short, int, their wrappers, String (since Java 7), and enums (since Java 5). Switching on a long gives error: selector type long is not allowed in recent JDKs (older ones said incompatible types: possible lossy conversion from long to int).
Labels must be constant expressions and unique. A final int limit = 3; local is a constant and works; a plain int limit = 3; is not and fails with constant expression required. Two case 1: labels fail with duplicate case label.
For an enum selector, labels are the bare constant names: case RED:, not case Color.RED: (qualified names are allowed only from Java 21).
enum Light { RED, YELLOW, GREEN }
Light light = Light.YELLOW;
switch (light) {
case RED: System.out.println("Stop"); break;
case YELLOW: System.out.println("Slow"); break;
case GREEN: System.out.println("Go"); break;
}
String command = "stop";
switch (command) { // String switch: Java 7+
case "start": System.out.println("Starting"); break;
case "stop": System.out.println("Stopping"); break;
default: System.out.println("Unknown command");
}05Scope inside a switch block
The whole switch body is one block. A variable declared under case 1: is visible (but not initialised) under case 2:, and declaring the same name again is variable x is already defined.
Give a case its own braces when it needs local variables: case 1: { int x = 10; ... break; }. The arrow form in Topic 2.4 gives every case its own scope automatically.
switch (op) {
case 1: {
int result = a + b; // scoped to these braces
System.out.println(result);
break;
}
case 2: {
int result = a - b; // a different variable, no clash
System.out.println(result);
break;
}
}06null selectors throw
A classic switch on a String, enum or wrapper first needs the actual value, so it calls a method on the selector (hashCode() for a String, ordinal() for an enum, intValue() for a wrapper). If the selector is null, that call throws NullPointerException before any case is considered, and default doesn't catch it.
Guard with if (command == null) first, or use Java 21's case null in a pattern switch.
07How the JVM jumps: tableswitch and lookupswitch
For dense int cases (1, 2, 3), javac emits a tableswitch: an array of jump targets indexed by value - low. Finding the right case is one subtraction, one bounds check and one jump, whatever the number of cases.
For sparse values (1, 100, 5000) a table would be mostly empty, so javac emits a lookupswitch: a sorted list of (value, target) pairs that the JVM can binary-search. Either way it's not a chain of comparisons like an else-if ladder. A String switch becomes a lookupswitch on hashCode(), followed by equals() to rule out hash collisions, then a second, dense switch on the matched index.
Try it yourself
- 1
Watch fall-through happen
Run 'The missing-break bug'. Then change
dayto 1 and predict the output before running (all four lines). Change it to 9: only 'Some other day' prints. - 2
Add a case with a shared action
In 'Switching on String, char and enum', make
case "halt"behave exactly like"stop"by stacking the labels, then rewrite it ascase "stop", "halt":. Both compile on Java 14 and later. - 3
Trigger the null case
Set
String command = null;and run. Read the exception: it namesString.hashCode(), which proves the switch callshashCode()first. Addif (command == null) { System.out.println("No command"); } else { ... }around the switch to fix it.
Code & diagrams
Expected output
1 -> Monday
3 -> Wednesday
9 -> unknownExpected output
Switch on 2 without break:
Tuesday
Wednesday
Some other dayEach case adds one and deliberately falls into the next. Entering at Wednesday (3) adds Wednesday, Thursday and Friday.
Expected output
Monday: 5
Wednesday: 3
Friday: 1
Sunday: 0Expected output
Stopping
Well done
Slow downA hand-written equivalent of the code javac generates. You never write this yourself; it explains why a null String throws and why the cost is one hash plus one equals.
// switch (command) { case "start": A; break; case "stop": B; break; default: C; }
int index = -1;
switch (command.hashCode()) { // NullPointerException if command is null
case 109757538: // "start".hashCode()
if (command.equals("start")) index = 0;
break;
case 3540994: // "stop".hashCode()
if (command.equals("stop")) index = 1;
break;
}
switch (index) {
case 0: A; break;
case 1: B; break;
default: C;
}Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use a variable as a case label
Declare int limit = 3; and write case limit:.
public class Main {
public static void main(String[] args) {
int x = 5;
int limit = 3;
switch (x) {
case limit: System.out.println("a");
}
}
}Break #2
Repeat a case label
Write case 1: twice in the same switch.
Break #3
Switch on a long
Declare long id = 2; and write switch (id).
Myth vs fact
Myth
default must be the last label.
Fact
It can go anywhere, and fall-through applies to it like any other label. Putting it last is a readability convention.
Myth
A switch checks the cases one by one, like an else-if ladder.
Fact
For ints it compiles to a tableswitch (one indexed jump) or lookupswitch (a search of sorted keys).
Myth
default catches a null selector.
Fact
A classic switch on null throws NullPointerException before any label is considered.
Myth
Switch on String compares with ==.
Fact
It uses hashCode() and then equals(), so it compares contents, like equals does.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
javacchooses betweentableswitchandlookupswitchwith a cost estimate: roughly, a table is used when its size isn't much bigger than the number of cases. The JIT may further compile either into a jump table, a binary search, or a few compares, depending on the profile. - ▸
A switch on an enum from another class compiles to a lookup in a synthetic array (
$SwitchMap$...) mappingordinal()to a case index, so reordering enum constants in a library doesn't break already-compiled switches. Since Java 21, pattern-style enum switches may instead useinvokedynamicwithSwitchBootstraps. - ▸
javac -Xlint:fallthroughwarns on every case that falls into the next with code in between. Many teams turn it on and mark intended fall-through with a// fall throughcomment (tools like Error Prone and Checkstyle recognise it). - ▸
Constant folding makes
static finalfields usable as labels, and it also means their values are inlined into the calling class at compile time. Change the constant in a library without recompiling the caller and the old value stays baked into its switch.
Remember this
- 1
The value being tested is the selector. In a classic switch it may be a
char,byte,short,int, their wrappers (Character,Byte,Short,Integer), anenum(Java 5), or aString(Java 7).long,float,doubleandbooleanare not allowed (until pattern matching in Java 21, Topic 2.4 shows that). - 2
Each
caselabel must be a compile-time constant: a literal like3or"stop", an enum constant, or astatic finalvariable initialised with a constant. Labels must be unique. A normal variable as a label is a compile error (constant expression required). - 3
In the colon form (
case 1:), once Java jumps to a case it keeps running every statement below it, through the following cases, until it reaches abreakor the end of the switch. This is fall-through. Forgettingbreakis the classic switch bug; using fall-through on purpose lets several cases share code. - 4
default:runs when no case matches. It's optional in a switch statement, and if it's missing and nothing matches, nothing happens. It may appear anywhere, but put it last so readers find it. - 5
A
nullselector (aString, enum or wrapper that isnull) throwsNullPointerExceptionbefore any case is checked. A classic switch has no way to matchnull; Java 21 addscase null(Topic 2.4). - 6
The compiler turns a switch on ints into a jump table (
tableswitch) when the case values are close together, or a sorted lookup (lookupswitch) when they're spread out. A switch onStringis compiled into a switch onhashCode(), then anequals()check.
Explain it without notes
What is fall-through in a switch statement, and when is it useful?
Which types can a classic switch statement select on, and why can't it use long?
What happens when a switch's String selector is null?
How does the JVM execute a switch on int, and how does a String switch work?
Practice
Write a switch that converts a month number (1–12) to the number of days in it, for a non-leap year, grouping months that share a length.
Write a calculator: given char op and two ints, use a switch to print the result for +, -, * and /, and Unknown operator otherwise. Test with '*', '/' and '%' on 12 and 4.
Using an enum Size { SMALL, MEDIUM, LARGE } and a switch, print the price of a coffee: 2, 3 and 4 respectively, for all three sizes.
Trade-offs
- ↔
A switch is clearer than an else-if ladder for one value against many constants, but it can't test ranges or combine conditions (until pattern guards in Java 21).
- ↔
Intentional fall-through is compact but easy to misread and easy to break when someone adds a case. Prefer comma-separated labels or the arrow form when you can.
- ↔
The classic switch statement is still everywhere in existing code, so you must read it fluently, but for new code that computes a value, the switch expression (Topic 2.4) is safer.
Done when you can
Done when you can write a classic switch with break and default from memory.
Done when you can predict the output of a switch with missing breaks.
Done when you can list the allowed selector types and explain why case labels must be constants.
Done when you can explain why a null String selector throws, and guard against it.
Done when you can describe tableswitch vs lookupswitch and how a String switch is compiled.