Switch Expressions (JEP 361): Switch as an Expression, Not Just a Statement
Finalized in Java 14 (JEP 361). Available in all Java 14+ releases, including Java 17. Previous previews: Java 12 (JEP 325) and Java 13 (JEP 354).
The Problem with Switch Statements
Java’s traditional switch statement has two well-known pain points:
Pain 1: Fall-Through
// Java 11 — easy to introduce fall-through bugs
switch (day) {
case MONDAY:
result = "Start of work week";
break; // easy to forget this
case TUESDAY:
result = "Weekday";
// no break — falls through to WEDNESDAY case
case WEDNESDAY:
result = "Hump day";
break;
default:
result = "Other";
}
A missing break causes the next case to execute unexpectedly. This is a classic Java bug — the compiler has no way to warn you that fall-through was unintentional.
Pain 2: Switch Is a Statement, Not an Expression
Switch in Java 11 is a statement — it cannot return a value directly. To use switch to assign a variable, you must pre-declare it:
// Java 11 — pre-declare, then assign inside switch
String result;
switch (day) {
case MONDAY:
case FRIDAY:
result = "Bookend";
break;
case WEDNESDAY:
result = "Middle";
break;
default:
result = "Other";
}
// result might be uninitialized if no case matched and no default
This pattern is verbose and the compiler cannot easily verify that result is definitely assigned.
Switch Expressions: The Solution
Java 14 introduces switch expressions — switch as a first-class expression that returns a value.
Arrow Switch Expression
// Java 17 — switch expression with arrow syntax
String result = switch (day) {
case MONDAY, FRIDAY -> "Bookend";
case TUESDAY, THURSDAY -> "Midweek";
case WEDNESDAY -> "Hump day";
case SATURDAY, SUNDAY -> "Weekend";
};
Key differences from the statement form:
- No
breakneeded — each case ends at->. No fall-through. - Multiple labels —
case MONDAY, FRIDAY ->handles both in one case. - Expression, not statement — the result is assigned directly to
result. - Exhaustiveness — the compiler verifies all enum values are covered (or there is a
default).
Traditional Switch Statements Still Work
Switch expressions do not replace switch statements. The traditional form is still valid. Arrow syntax can also be used in switch statements (without producing a value):
// Arrow syntax in a statement context (no value produced)
switch (level) {
case INFO -> logger.info(message);
case WARN -> logger.warn(message);
case ERROR -> logger.error(message);
}
Arrow Cases vs Colon Cases
Switch expressions support both syntaxes:
Arrow Syntax (->)
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
- No fall-through
- Single expression or block per case
- Most idiomatic for switch expressions
Colon Syntax with yield
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY:
yield 6;
case TUESDAY:
yield 7;
case THURSDAY, SATURDAY:
yield 8;
case WEDNESDAY:
yield 9;
};
Colon syntax with yield exists to support legacy-style switch in expression context. Arrow syntax is preferred in new code.
The yield Statement
yield produces a value from a multi-statement case block in a switch expression:
String description = switch (code) {
case 200 -> "OK";
case 404 -> "Not Found";
case 500 -> {
String msg = "Server Error";
log.error("HTTP 500 encountered");
yield msg; // return value from the block
}
default -> "Unknown";
};
yield is only valid inside a switch expression. In a switch statement, you still use break. This distinction is intentional — yield makes the control flow clear.
return inside a switch expression returns from the enclosing method (not from the case). This is different from yield, which produces the switch expression’s value.
Exhaustiveness
Switch expressions on sealed types and enums are checked for exhaustiveness by the compiler:
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
// Missing SATURDAY and SUNDAY — compile error
String type = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
// error: switch expression does not cover all possible input values
};
// Correct: all cases or default
String type = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
case SATURDAY, SUNDAY -> "Weekend";
};
Exhaustiveness at compile time means no default is needed when all enum values are covered — and you get a compile error if you add a new enum value without updating all switch expressions.
This is one of the main motivations for sealed classes (Article 7): they enable the compiler to verify exhaustiveness for arbitrary type hierarchies, not just enums.
Switch Expressions with Sealed Classes (Preview in Java 17)
In Java 17, switching on sealed types with pattern matching is a preview feature (Article 8). But switching on sealed types by instance is not yet supported without --enable-preview. The combination becomes final in Java 21.
In Java 17 without preview, you can switch on the value of sealed class instances using standard equality-based cases:
sealed interface Status permits Active, Inactive, Suspended {}
record Active() implements Status {}
record Inactive() implements Status {}
record Suspended(String reason) implements Status {}
// This requires --enable-preview in Java 17 (Article 8)
// In Java 17 without preview, use instanceof chain
Switch on Strings, Integers, and Enums
Switch expressions work on all types supported by traditional switch: int, Integer, long, Long, String, and enum types.
String Switch
String response = switch (httpMethod.toUpperCase()) {
case "GET" -> fetchResource(id);
case "POST" -> createResource(body);
case "PUT" -> updateResource(id, body);
case "DELETE" -> deleteResource(id);
default -> throw new UnsupportedOperationException(httpMethod);
};
Integer Switch
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
default -> "F";
};
Throwing Exceptions in Switch Expressions
A case can throw an exception — this satisfies the requirement that every path produces a value (or throws):
String season = 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);
};
Returning Switch Expressions from Methods
Switch expressions can be used directly in return statements:
static String format(Object value) {
return switch (value) {
case Integer i -> "Int: " + i;
case Double d -> "Double: " + String.format("%.2f", d);
case String s -> "String: \"" + s + "\"";
default -> "Unknown: " + value;
}; // Note: this is a preview feature in Java 17 — see Article 8
}
Non-pattern switch expressions in return:
static String dayType(DayOfWeek day) {
return switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
case SATURDAY, SUNDAY -> "Weekend";
};
}
Common Patterns
Configuration Selection
DataSource dataSource = switch (environment) {
case "dev" -> new H2DataSource();
case "test" -> new TestContainerDataSource();
case "prod" -> new PostgresDataSource(config);
default -> throw new IllegalStateException("Unknown env: " + environment);
};
Enum to Value Mapping
enum Priority { LOW, MEDIUM, HIGH, CRITICAL }
int slaHours = switch (priority) {
case LOW -> 72;
case MEDIUM -> 24;
case HIGH -> 4;
case CRITICAL -> 1;
};
Multi-Statement Case with Logging
String status = switch (code) {
case 0 -> "Success";
case 1 -> "Partial failure";
case -1 -> {
metrics.increment("errors");
log.error("Critical error code -1");
yield "Critical failure";
}
default -> {
log.warn("Unknown code: " + code);
yield "Unknown";
}
};
Switch Expressions vs Ternary and if-else
| Construct | Use case |
|---|---|
Ternary ? : | Two alternatives, simple expressions |
if-else if-else | Complex conditions, side effects |
| Switch expression | Multiple alternatives based on a single value |
| Switch statement | Multiple alternatives with side effects |
Switch expressions are most valuable when you have 3 or more cases based on a discrete value.
What Cannot Be in a Switch Expression
- No
return(to return from enclosing method — use at the method level, not inside a case) - No
break(breaks are for switch statements) - No
continue(for loop continuation) - An arrow case must have a single expression, a block, or a throw — not multiple statements without a block
Summary
| Feature | Detail |
|---|---|
| Arrow syntax | case X -> expr; — no fall-through |
| Multiple labels | case A, B, C -> |
| Multi-statement case | case X -> { ... yield value; } |
yield | Returns value from a block inside switch expression |
| Exhaustiveness | Compiler-enforced for enums; requires default for other types |
| Null handling | null is not matched by any case; throws NullPointerException at the switch |
| Colon syntax | case X: yield value; — supports fall-through, less idiomatic |
What’s Next
Article 7: Sealed Classes (JEP 409) covers how to define closed type hierarchies where the compiler knows all possible subtypes — enabling exhaustive switch without default.