JAVA / BRANCHING AND SWITCH EXPRESSIONS
switch statements and fall-through behaviour
Read and write colon-style Java switch statements, place break correctly, and use fall-through deliberately instead of being surprised by it.
What you will learn
- Trace any switch by finding the entry label, then reading downward until a break
- Stack case labels with empty bodies so several values share one body
- Order cases so a match also runs the branches below it, on purpose
- Spot the missing-break bug where a later case overwrites an earlier assignment
Understanding switch statements and fall-through behaviour
A colon-style switch is not a chain of independent branches; it is a single block of statements with labels attached to some of them. Evaluating the selector performs one equality test to choose a label, and control jumps there, which is the entire matching step. From that point execution continues in source order, straight through any further case labels, because a label names a position rather than a boundary. That continuation is called fall-through, and in a colon switch it is the default.
The statement that stops it is break, which transfers control to the first statement after the switch's closing brace. return and throw also leave, since they abandon the enclosing method, and continue inside a loop leaves the switch and starts the next iteration. Because break is optional in the grammar, forgetting one is not a compile error and javac says nothing by default, so a missing break surfaces as a wrong value at runtime instead of a failed build.
Fall-through earns its keep in two shapes. Stacking labels with nothing between them lets several values share one body, since an empty case falls into the next label immediately. Ordering cases so each does its own work and then drops into the next gives cumulative behaviour, such as level 3 granting delete and then everything levels 2 and 1 grant. The price is that source order becomes part of the meaning: moving a case inside a switch that relies on fall-through changes what the program does, whereas the branches of an if/else chain are independent of each other.
public class Fallthrough {
public static void main(String[] args) {
for (int level = 1; level <= 3; level++) {
System.out.println("level " + level + " grants:");
switch (level) {
case 3:
System.out.println(" delete");
// falls through
case 2:
System.out.println(" write");
// falls through
case 1:
System.out.println(" read");
break;
default:
System.out.println(" nothing");
}
}
}
}Matching a case label only decides where execution enters the switch block; it then keeps running downward through later labels until a break, return, throw or the closing brace stops it.
Worked examples
Stacked labels sharing one body
Five case labels with no statements between them all run the same counter update.
public class Grouped {
public static void main(String[] args) {
int vowels = 0;
int others = 0;
for (char c : "education".toCharArray()) {
switch (c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
vowels++;
break;
default:
others++;
}
}
System.out.println(vowels + " vowels, " + others + " others");
}
}Example explained
Line 1case 'a': has no statements at all, so entering there falls straight into the next label and eventually reaches vowels++.
Line 2The break after vowels++ is what keeps a vowel from also reaching default and being counted twice.
Line 3default needs no break because it is the last thing in the block, so control leaves at the closing brace anyway.
Line 4Switching on char works because char is one of the integral selector types; the labels are single-quoted char literals, not strings.
The missing-break bug
Assignments in successive cases overwrite each other when no break separates them.
public class MissingBreak {
public static void main(String[] args) {
System.out.println(describe(1));
System.out.println(describe(2));
System.out.println(describe(4));
}
static String describe(int code) {
String result = "?";
switch (code) {
case 1:
result = "start";
case 2:
result = "stop";
case 9:
result = "reset";
}
return result;
}
}Example explained
Line 1describe(1) enters at case 1 and assigns "start", then keeps running: case 2 overwrites it with "stop" and case 9 with "reset".
Line 2With no break anywhere, the only value a matching call can return is the last assignment in the block, so 1 and 2 give identical answers.
Line 3describe(4) matches no label and there is no default, so the entire block is skipped and the initial "?" survives.
Line 4Writing return "stop"; instead of the assignment would fix it, because return exits the method and therefore the switch.
default is just another label
A default clause placed in the middle of the block falls through into the case below it.
public class DefaultInMiddle {
public static void main(String[] args) {
String[] commands = {"up", "sideways", "down"};
for (String cmd : commands) {
System.out.print(cmd + " -> ");
switch (cmd) {
case "up":
System.out.println("y+1");
break;
default:
System.out.print("unknown, treating as ");
// falls through
case "down":
System.out.println("y-1");
break;
}
}
}
}Example explained
Line 1The selector is a String, so each label is compared using equals; a null cmd would throw NullPointerException before any label is tested.
Line 2default is only reached after every case label fails to match, but it sits above case "down", so its statements run and then case "down"'s statements run.
Line 3"down" jumps directly to its own label and never prints the default text, showing that position in the source decides what follows, not the kind of label.
Line 4The final break could be deleted with no change in behaviour, since it is the last statement in the block.
Important notes
javac reports nothing about fall-through unless you pass -Xlint:fallthrough, and that warning is silenced only by @SuppressWarnings("fallthrough"), not by the conventional // falls through comment, which is for humans and other tools. The warning also ignores stacked labels with empty bodies, since those are unambiguously intentional.
Case labels must be compile-time constants (literals, static final constants, enum names) and must all be distinct; duplicates are a compile error, not a first-match-wins situation.
Common mistakes
Omitting a break so two cases both execute: the later statements silently win, and every value from the matching label downward produces the same result, as describe(1) and describe(2) both returning "reset".
Using break inside a switch that sits inside a loop and expecting the loop to end; break only leaves the switch, so the loop keeps iterating, and ending the loop needs a labelled break.
Declaring a variable such as int n = 5; inside one colon case and reading it in another: all case labels share one block scope, so the name is visible but the compiler rejects the read with "variable n might not have been initialized".
Try it yourself
Change, predict, then run
Write a switch on an int month that prints 31, 30 or 28, using one stacked group of labels for the four 30-day months. Then delete the break at the end of that group and predict, before running it, exactly which months now print two numbers.
Open the Java workspaceCheck your understanding
A colon-style switch has cases 1, 2 and 3, each printing its own line, and only case 3 ends with break. Switching on 1 prints all three lines, but switching on 3 prints only one. What does that reveal?
- The label only chooses where execution enters the block; it then continues through the following statements until a break
- Every label whose value is less than or equal to the selector matches, so lower cases run as well
- The missing break statements cause the value 1 to be re-tested against cases 2 and 3, which then also match
- Cases are evaluated top to bottom and the last matching case wins, so the final printed line is the real result
Show answer
The selector is compared once to pick an entry label, and the statements after it run because nothing transfers control out of the block; case 2 and case 3 never matched the value 1 at all. The "less than or equal" option is tempting because the output looks cumulative, but it would predict that switching on 3 also prints three lines, and it prints only one.