☕ Java
switch Expression
The switch expression (Java 14+) is a modern alternative to the switch statement that produces a value. It uses arrow labels (case value ->) that eliminate fall-through and the need for break statements. Multiple values can be combined in a single case. The yield keyword returns a value from a multi-statement case block. Switch expressions are exhaustive — the compiler verifies that all possible input values are covered.
Arrow Syntax and Basic Usage
Switch expressions use the arrow (->) syntax instead of colon (:). Each arrow case executes exactly one expression, block, or throw — there is no fall-through and no break needed. The entire switch expression produces a single value that can be assigned to a variable, returned from a method, or passed as a method argument.
Java
// ── Traditional switch statement (old): ─────────────────────────────
int day = 3;
String dayName;
switch (day) {
case 1: dayName = "Monday"; break;
case 2: dayName = "Tuesday"; break;
case 3: dayName = "Wednesday"; break;
default: dayName = "Other"; break;
}
// ── Switch expression (Java 14+) — same logic, much cleaner: ─────────
String dayName2 = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday"; // selected — day = 3
case 4 -> "Thursday";
case 5 -> "Friday";
case 6 -> "Saturday";
case 7 -> "Sunday";
default -> "Invalid day";
};
System.out.println(dayName2); // Wednesday
// ── Switch expression as method return value: ─────────────────────────
public static String getSeasonName(int month) {
return switch (month) {
case 12, 1, 2 -> "Winter";
case 3, 4, 5 -> "Spring";
case 6, 7, 8 -> "Summer";
case 9, 10, 11 -> "Autumn";
default -> throw new IllegalArgumentException(
"Invalid month: " + month);
};
}
// ── Switch expression as method argument: ─────────────────────────────
System.out.println(switch (day) {
case 6, 7 -> "Weekend";
default -> "Weekday";
});
// ── Key differences from switch statement: ────────────────────────────
//
// Arrow labels (->):
// • No fall-through — each case is completely isolated
// • No break needed — arrow case runs exactly one thing
// • Can be expression, block, or throw
//
// Multiple values per case (case 1, 2, 3 ->):
// • Replaces stacked empty cases from fall-through pattern
// • More readable and less error-prone
//
// Produces a value:
// • Can be assigned, returned, or passed as argument
// • The compiler verifies all cases return the same type
//
// Exhaustiveness:
// • Compiler error if not all values are covered
// • For enums: must cover all constants or have default
// • For int/String: default is requiredMultiple Case Labels
Switch expressions can group multiple values in a single case using comma separation. This replaces the fall-through stacking pattern from traditional switch statements with a clean, explicit syntax. Multiple labels mean exactly the same as separate cases — any of the listed values triggers that arm.
Java
// ── Multiple values with arrow case: ────────────────────────────────
int month = 4;
int daysInMonth = switch (month) {
case 1, 3, 5, 7, 8, 10, 12 -> 31; // all 31-day months in one line
case 4, 6, 9, 11 -> 30; // all 30-day months in one line
case 2 -> 28; // February (simplified)
default -> throw new IllegalArgumentException(
"Month must be 1-12, got: " + month);
};
System.out.println("Days: " + daysInMonth); // Days: 30
// ── Compare with old fall-through pattern: ────────────────────────────
// OLD (8 lines):
// case 1: case 3: case 5: case 7: case 8: case 10: case 12:
// daysInMonth = 31; break;
// NEW (1 line):
// case 1, 3, 5, 7, 8, 10, 12 -> 31;
// ── String switch with multiple labels: ──────────────────────────────
String command = "quit";
switch (command.toLowerCase()) {
case "start", "begin", "run" -> startService();
case "stop", "halt" -> stopService();
case "quit", "exit", "bye" -> {
System.out.println("Goodbye.");
System.exit(0);
}
default -> System.out.println("Unknown: " + command);
}
// ── Enum switch — multiple labels: ────────────────────────────────────
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
Day today = Day.SAT;
String type = switch (today) {
case MON, TUE, WED, THU, FRI -> "Weekday";
case SAT, SUN -> "Weekend"; // selected
};
System.out.println(today + " is a " + type); // SAT is a Weekend
// ── Enum switch without default — compiler checks exhaustiveness: ─────
// If all enum constants are covered, default is NOT required.
// Adding a new enum constant without updating the switch
// will produce a compile error — a useful safety net.
int hoursWorked = switch (today) {
case MON, TUE, WED, THU, FRI -> 8;
case SAT -> 4;
case SUN -> 0;
// No default needed — all 7 Day constants are covered.
};yield — Multi-Statement Cases
When a case requires more than a single expression — for example, local variable declarations, loops, or conditional logic — use a block body enclosed in braces. The yield keyword returns the value from the block, replacing what break did for values in earlier proposals. yield is mandatory in block cases; it is not used in arrow expression cases.
Java
// ── yield in a block case: ───────────────────────────────────────────
int score = 73;
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> {
// Block body — can contain multiple statements:
String base = "C";
String modifier = (score >= 75) ? "+" : "";
yield base + modifier; // yield returns the value from this block
}
case 6 -> "D";
default -> {
if (score < 0 || score > 100) {
throw new IllegalArgumentException(
"Score out of range: " + score);
}
yield "F";
}
};
System.out.println("Grade: " + grade); // Grade: C
// ── yield vs return: ─────────────────────────────────────────────────
// yield → returns a VALUE from a switch expression block case
// return → returns from the ENCLOSING METHOD
//
// Inside a switch expression block:
// yield value; ← correct — returns value from this case
// return value; ← returns from the method (if inside a method)
// ── Mixing arrow and block cases: ────────────────────────────────────
int statusCode = 404;
String message = switch (statusCode) {
case 200 -> "OK"; // arrow — simple
case 201 -> "Created";
case 400 -> "Bad Request";
case 404 -> {
// Block — log and return value:
System.err.println("Resource not found: " + statusCode);
yield "Not Found"; // yield in block
}
case 500 -> {
System.err.println("Server error occurred");
yield "Internal Server Error";
}
default -> {
String msg = "Unexpected status: " + statusCode;
System.err.println(msg);
yield msg;
}
};
// ── yield in traditional colon switch expression: ─────────────────────
// Switch expressions can also use colon (:) syntax — yield replaces break:
int val = 2;
int result = switch (val) {
case 1:
System.out.println("Processing 1");
yield 100; // yield instead of break
case 2:
System.out.println("Processing 2");
yield 200; // executes — yield 200
default:
yield -1;
};
System.out.println("Result: " + result); // Result: 200Exhaustiveness and the default Case
Switch expressions must be exhaustive — the compiler verifies that every possible value of the selector expression is covered. For enum types, covering all constants satisfies the compiler without a default. For int, String, and other types, a default case is required because the set of possible values is effectively infinite. Adding a new constant to an enum without updating all switch expressions that cover it individually produces a compile error.
Java
// ── Enum — exhaustive without default: ───────────────────────────────
enum Status { PENDING, ACTIVE, INACTIVE, SUSPENDED }
Status status = Status.ACTIVE;
String label = switch (status) {
case PENDING -> "Awaiting approval";
case ACTIVE -> "Running"; // selected
case INACTIVE -> "Stopped";
case SUSPENDED -> "Temporarily halted";
// No default — all 4 Status constants are covered.
// Compiler error if a new constant is added to Status
// without updating this switch.
};
// ── Adding default "just in case" for enum (defensive): ──────────────
String label2 = switch (status) {
case PENDING -> "Awaiting approval";
case ACTIVE -> "Running";
case INACTIVE -> "Stopped";
case SUSPENDED -> "Temporarily halted";
default -> throw new IllegalStateException(
"Unhandled status: " + status);
// If a new Status constant is added, this throws rather
// than silently returning null or a wrong value.
};
// ── int — default is required: ───────────────────────────────────────
int code = 2;
// COMPILE ERROR — missing default:
// String name = switch (code) {
// case 1 -> "One";
// case 2 -> "Two";
// };
// CORRECT — default covers all other int values:
String name = switch (code) {
case 1 -> "One";
case 2 -> "Two"; // selected
case 3 -> "Three";
default -> "Unknown: " + code;
};
// ── String — default is required: ────────────────────────────────────
String input = "go";
String action = switch (input) {
case "stop" -> "Stopping";
case "start" -> "Starting";
case "go" -> "Going"; // selected
default -> "Unknown command: " + input;
};
// ── Compile-time exhaustiveness check protects against refactoring: ───
// Scenario: team adds DELETED to the Status enum.
// All switch expressions covering all constants individually now
// produce a compile error at EVERY usage site — forcing developers
// to consciously handle the new constant rather than silently missing it.
// This is the main reason to avoid default in enum switches.Pattern Matching in switch (Java 21+)
Java 21 extended switch expressions and statements with pattern matching. A case can now match by type (case Integer i), apply a guard condition (case Integer i when i > 0), and match null explicitly. This replaces chains of instanceof checks with a single expressive switch. The selector expression may be any reference type including Object.
Java
// ── Type patterns in switch (Java 21+): ─────────────────────────────
Object obj = 42;
String description = switch (obj) {
case Integer i -> "Integer: " + i; // selected — obj is Integer
case String s -> "String of length " + s.length();
case Double d -> "Double: " + d;
case int[] ia -> "int array of length " + ia.length;
case null -> "null value";
default -> "Other type: " + obj.getClass().getName();
};
System.out.println(description); // Integer: 42
// ── Guard conditions — when clause: ──────────────────────────────────
Object value = -5;
String category = switch (value) {
case Integer i when i > 0 -> "Positive integer: " + i;
case Integer i when i < 0 -> "Negative integer: " + i; // selected
case Integer i -> "Zero";
case String s when !s.isBlank() -> "Non-blank string: " + s;
case String s -> "Blank string";
case null -> "Null";
default -> "Other: " + value;
};
System.out.println(category); // Negative integer: -5
// ── Replaces instanceof chains: ───────────────────────────────────────
// OLD — verbose instanceof chain:
public double calculateArea(Shape shape) {
if (shape instanceof Circle c) {
return Math.PI * c.radius() * c.radius();
} else if (shape instanceof Rectangle r) {
return r.width() * r.height();
} else if (shape instanceof Triangle t) {
return 0.5 * t.base() * t.height();
} else {
throw new IllegalArgumentException("Unknown shape");
}
}
// NEW — pattern switch (Java 21+):
public double calculateArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
default -> throw new IllegalArgumentException(
"Unknown shape: " + shape);
};
}
// ── Sealed classes + pattern switch — exhaustive without default: ─────
// sealed interface Shape permits Circle, Rectangle, Triangle {}
public String describeShape(Shape shape) {
return switch (shape) {
case Circle c -> "Circle r=" + c.radius();
case Rectangle r -> "Rectangle " + r.width() + "x" + r.height();
case Triangle t -> "Triangle b=" + t.base();
// No default needed — sealed hierarchy is exhaustive.
};
}switch Statement vs switch Expression — Comparison
Switch expressions were introduced to address the well-known problems of the traditional switch statement — accidental fall-through, verbose break statements, and the inability to produce a value directly. Understanding the differences helps choose the right form for each situation.
Java
// ── Side-by-side comparison: ─────────────────────────────────────────
// Traditional switch STATEMENT:
String result1;
switch (day) {
case 1:
result1 = "Monday";
break; // break required
case 2:
result1 = "Tuesday";
break;
default:
result1 = "Other";
break;
}
// Problems:
// • Verbose — break on every case
// • Fall-through risk — missing break is silent bug
// • result1 must be declared before switch
// • Compiler cannot verify result1 is always assigned
// (if default is omitted, result1 may be uninitialised)
// Modern switch EXPRESSION (Java 14+):
String result2 = switch (day) {
case 1 -> "Monday"; // no break needed
case 2 -> "Tuesday";
default -> "Other";
};
// Advantages:
// • No fall-through — arrows are isolated
// • No break needed
// • Value assigned inline — no separate declaration
// • Compiler verifies exhaustiveness
// • All cases must produce a value of the same type
// ── When to use which: ────────────────────────────────────────────────
//
// Use switch EXPRESSION when:
// Computing a value based on a selector
// Returning from a method based on a selector
// Replacing if-else-if chains that produce a value
// Any new code targeting Java 14+
//
// Use switch STATEMENT when:
// Executing side effects (print, update state, call void methods)
// Intentional fall-through is needed (rare)
// Maintaining Java 8–13 compatibility
// ── switch expression producing void (statement-like usage): ─────────
// Switch expressions can execute side effects too:
switch (command) {
case "START" -> startService(); // void method — no value needed
case "STOP" -> stopService();
default -> System.out.println("Unknown: " + command);
}
// This is still a switch expression syntactically, but used
// as a statement (its value, if any, is discarded).Related Topics in Control Statements
Control Statements
Control statements determine the flow of execution in a Java program. Without them, code executes line by line from top to bottom. Control statements allow the program to make decisions, repeat actions, and jump to different parts of the code based on conditions. Java provides three categories: selection statements (if, if-else, switch), iteration statements (for, while, do-while), and jump statements (break, continue, return).
if Statement
The if statement is the most fundamental control flow construct in Java. It evaluates a boolean expression and executes a block of code only if the expression is true. If the condition is false the block is skipped entirely and execution continues with the next statement after the if block.
if-else Statement
The if-else statement extends the basic if by providing an alternative block that executes when the condition is false. It guarantees that exactly one of two blocks always executes — either the if block (condition true) or the else block (condition false). The if-else-if chain extends this to choose among more than two alternatives.
Nested if
A nested if is an if statement placed inside the body of another if or else block. Nesting allows multi-level decision making — first check a broad condition, then refine with a more specific condition inside it. While nesting is sometimes necessary, deep nesting quickly reduces readability and should be refactored using guard clauses, logical operators, or extracted methods.