2.2 Switch Statements and Switch Expressions
Key Takeaways
Selector expressions in standard Java SE 17 accept byte, short, char, int, their respective boxed wrappers, String, and enums; long, float, double, and boolean are disallowed.
Case labels must be compile-time constants such as literals, constant expressions, or final variables initialized at declaration; non-constant variables cause compilation failures.
Classic switch statements using colon (:) syntax exhibit fall-through across cases unless terminated by break or return, and share a single variable scope across all cases.
Arrow labels (->) never fall through and allow comma-separated case values; switch expressions return values directly or with the yield keyword in multi-statement blocks, while colon-form switch expressions still fall through.
Switch expressions that produce a value must be exhaustive; a default branch is required unless all constants of an enum selector are explicitly handled.
2.2 Switch Statements and Switch Expressions
The switch construct in Java provides multi-way branching based on the value of a selector expression. Since switch expressions were standardized in Java 14 (JEP 361), the language supports two label forms (colon case X: and arrow case X ->) and two uses of switch (as a statement that performs actions, or as an expression that produces a value with yield where needed). Java SE 17 LTS includes both. The 1Z0-829 exam tests deep knowledge of permitted selector types, compile-time constant requirements for case labels, fall-through rules, variable scoping, and exhaustiveness checks.
Note
Pattern matching for switch (e.g., case Integer i -> ...) was a preview feature in Java 17 (JEP 406) and was not finalized until Java 21 (JEP 441). The Java SE 17 Developer exam (1Z0-829) covers standard, non-preview language features: classic switch statements and standard switch expressions.
Permitted and Disallowed Selector Types
Java restricts the data types that may serve as the selector expression in a switch construct.
| Permitted Selector Types | Disallowed Selector Types |
|---|---|
byte, short, char, int (primitives) | long (and java.lang.Long) |
Byte, Short, Character, Integer (wrappers) | float (and java.lang.Float) |
java.lang.String | double (and java.lang.Double) |
enum types | boolean (and java.lang.Boolean) |
| Arbitrary Object references |
The null Selector Trap
If the selector expression evaluates to null at runtime—whether it is a boxed wrapper (such as Integer) or a String reference—Java immediately throws a NullPointerException upon entering the switch construct:
String input = null;
// Throws NullPointerException before checking any case or default!
switch (input) {
case "admin":
System.out.println("Administrator");
break;
default:
System.out.println("Guest");
}
The switch does not fall into the default branch when the selector is null. A runtime exception is thrown immediately.
Classic Switch Statements: Constant Expressions & Fall-Through
In classic switch statements, each case label must be a compile-time constant expression whose type is assignment-compatible with the switch selector:
- Literals: e.g.,
case 1:,case "READY":. - Compile-Time Constant Variables: A variable declared
finaland initialized at the point of declaration with a constant expression (e.g.,final int BASE = 10;). - Enum Constants: When switching on an enum type, the case label must specify the unqualified name of the enum constant (e.g.,
case NORTH:, notcase Direction.NORTH:).
final int MAX = 100;
final int UNASSIGNED;
UNASSIGNED = 50; // Blank final: NOT a compile-time constant!
int dynamicValue = 20;
int test = 10;
switch (test) {
case MAX: // Legal: compile-time constant
break;
// case UNASSIGNED: // COMPILE ERROR: constant expression required
// break;
// case dynamicValue: // COMPILE ERROR: constant expression required
// break;
}
Furthermore, the value of a case label must fit within the range of the selector's primitive type. If switching on a byte (range -128 to 127), a case label of case 130: causes a compilation error: incompatible types: possible lossy conversion from int to byte.
Fall-Through Mechanics
Classic switch branches delimited with a colon (:) exhibit fall-through. If a matching case does not terminate with a break, return, throw, or yield, execution flows sequentially into the statements of subsequent cases regardless of whether those case labels match the selector:
int day = 2;
switch (day) {
case 1:
System.out.print("Mon ");
case 2:
System.out.print("Tue ");
case 3:
System.out.print("Wed ");
break;
default:
System.out.print("Other ");
}
// Outputs: "Tue Wed "
If day is 2, execution jumps directly to case 2:. It prints "Tue ", falls through to case 3:, prints "Wed ", and then encounters break, which terminates the switch.
Variable Scoping Across Switch Cases
A classic switch block forms a single scope for local variables between the opening { and closing }. A variable declared inside one case remains in scope for all subsequent case and default blocks:
int selector = 1;
switch (selector) {
case 1:
int value = 42;
System.out.println(value);
break;
case 2:
// COMPILE ERROR: variable value is already defined in scope!
int value = 99;
System.out.println(value);
break;
default:
// value is still in scope here, but might NOT be initialized!
// System.out.println(value); // COMPILE ERROR if used before initialization
}
To isolate variable declarations between cases, each branch must be enclosed in its own block using curly braces:
switch (selector) {
case 1 -> { int value = 42; System.out.println(value); }
case 2 -> { int value = 99; System.out.println(value); } // Legal in isolated blocks
}
Modern Switch Expressions (Java 14 / Java 17 LTS)
Java SE 17 provides modern switch expressions using the arrow (->) syntax. Switch expressions resolve many long-standing design traps of classic switches:
- No Fall-Through: Exactly the code on the right-hand side of the matching arrow executes, so no
breakis needed. (Abreakis still legal inside an arrow block of a switch statement, but aswitchexpression cannot be exited withbreak.) - Multiple Comma-Separated Labels: Multiple matching values can share a single branch separated by commas (
case 1, 2, 3 ->). - Value Production: A switch expression can return a value directly to a variable assignment or method call.
int month = 4;
int days = switch (month) {
case 1, 3, 5, 7, 8, 10, 12 -> 31;
case 4, 6, 9, 11 -> 30;
case 2 -> 28;
default -> 0;
};
The yield Keyword
When a branch in a switch expression requires multiple statements, the logic is enclosed in a block { ... }. To return a value from this block, Java uses the contextual keyword yield:
int code = 3;
String description = switch (code) {
case 1 -> "Low Priority";
case 2, 3 -> {
String prefix = "Urgent: Level ";
yield prefix + code; // Returns value from block
}
default -> "Standard";
};
Critical Rules Governing yield
yieldtakes an argument and passes it as the result of the enclosing switch expression.returnexits the entire enclosing method, whereasyieldonly exits the switch expression.- You cannot use
yieldinside a classic switch statement that does not return a value. - You cannot mix arrow (
->) syntax and colon (:) syntax within the same switch construct. A switch must use exclusively arrows or exclusively colons.
// COMPILE ERROR: cannot mix ':' and '->' in the same switch
int invalid = switch (code) {
case 1 -> 10;
case 2: yield 20;
default -> 0;
};
Exhaustive Matching Requirements
When a switch is used as an expression (evaluating to a value), the compiler strictly requires exhaustive coverage of all possible input values:
- Primitives and Strings: Because
int,char,String, etc., have effectively boundless or large value domains, adefaultbranch is mandatory. Omittingdefaultresults in a compile-time error:the switch expression does not cover all possible input values. - Enum Types: If the switch selector is an
enum, adefaultcase is optional if and only if every enum constant defined in the enumeration is explicitly handled by acaselabel:
enum Status { PENDING, ACTIVE, REJECTED }
Status current = Status.ACTIVE;
// Legal without 'default' because all 3 constants are covered:
int statusCode = switch (current) {
case PENDING -> 1;
case ACTIVE -> 2;
case REJECTED -> 3;
};
If a new enum constant (e.g., CANCELLED) is subsequently added to the enum definition, the switch expression will fail compilation until the new constant is handled or a default branch is provided.
When a switch is used as a statement (performing actions without producing a value), exhaustiveness is not required.
Note
Two independent choices. Fall-through depends on the label form, and exhaustiveness depends on whether the switch is an expression. A switch expression may use colon labels with yield (and then falls through like a classic switch), and a switch statement may use arrow labels (no fall-through, no exhaustiveness check). The comparison table below contrasts the two most common combinations: a colon-form statement and an arrow-form expression.
Comparison: Classic Switch vs. Switch Expression
| Feature | Classic Switch Statement | Modern Switch Expression |
|---|---|---|
| Primary Syntax | Colons (case VALUE:) | Arrows (case VALUE ->) |
| Fall-Through | Yes (automatic without break) | No (never falls through) |
| Multiple Labels per Branch | Stacked (case 1: case 2:) or comma-separated (case 1, 2:) | Comma-separated (case 1, 2 ->) |
| Value Production | Cannot evaluate to a value | Returns a value via direct expression or yield |
| Multi-line Block Termination | break; | yield value; |
| Exhaustiveness | Not enforced by compiler | Mandatory (must cover all values or have default) |
| Variable Scoping | Shared across entire switch block | Isolated to respective block {} branches |
Which of the following data types is NOT permitted as the selector expression in a Java SE 17 switch construct?
short
long
java.lang.String
java.lang.Character
Consider the following switch expression:
public class SwitchCalculation {
public static void main(String[] args) {
int code = 2;
int result = switch (code) {
case 1 -> 10;
case 2, 3 -> {
int factor = 4;
yield code * factor;
}
default -> 0;
};
System.out.println(result);
}
}
What is the outcome of compiling and running this code?
The code outputs 0
The code fails to compile because yield cannot be used inside an arrow branch block
The code outputs 12
The code outputs 8
What is the result of attempting to compile and run the following code?
public class SwitchScope {
public static void main(String[] args) {
int k = 1;
switch (k) {
case 1:
int val = 100;
System.out.print(val + " ");
break;
case 2:
int val = 200;
System.out.print(val);
break;
default:
System.out.print("Def");
}
}
}
Compilation fails because val is declared twice
The code outputs Def because no case matches k
The code outputs 100 because case 1 ends with break
The code outputs 100 200 because case 1 falls through
Consider the following class:
public class NullSwitch {
public static void main(String[] args) {
String text = null;
switch (text) {
case "A":
System.out.print("Alpha");
break;
case "B":
System.out.print("Beta");
break;
default:
System.out.print("Default");
}
}
}
What occurs when this code is executed?
It throws a NullPointerException at runtime before evaluating any case label
It fails to compile because null is not an accepted constant expression
It prints Default because null does not match "A" or "B"
It prints Alpha
Sections you finish are checked off in the contents.