Switch Expressions
Switch Expressions
Switch expressions, finalized in Java 14 (JEP 361), add an arrow-based form of switch that returns a value directly, requires no break, and does not fall through between cases by default — fixing the single most common source of bugs in the traditional switch statement, while also letting switch be used directly as an expression.
What Is a Switch Expression?
A switch expression is the arrow-based form of switch — case value ->, no break — that produces a value directly instead of requiring a variable declared and assigned outside the block. JEP 361's design goal was specifically to close off fall-through as a possibility entirely, not just discourage it through style guides, while also letting switch be used as an expression the way if/else already could be through the ternary operator.
Why Switch Expressions Were Introduced
A traditional switch statement needed a variable declared outside the block, an assignment in every branch, and an explicit break after each one to avoid falling into the next case.
1// File: BeforeSwitchExpressions.java
2
3public class BeforeSwitchExpressions {
4
5 static String dayType(String day) {
6 String result;
7 switch (day) {
8 case "MONDAY":
9 case "TUESDAY":
10 case "WEDNESDAY":
11 case "THURSDAY":
12 case "FRIDAY":
13 result = "Weekday";
14 break;
15 case "SATURDAY":
16 case "SUNDAY":
17 result = "Weekend";
18 break;
19 default:
20 result = "Unknown";
21 }
22 return result;
23 }
24
25 public static void main(String[] args) {
26 System.out.println(dayType("WEDNESDAY"));
27 System.out.println(dayType("SUNDAY"));
28 }
29}Output:
Weekday
Weekend
The arrow form returns a value directly, groups multiple matching values into one case label, and needs no break at all.
1// File: AfterSwitchExpressions.java
2
3public class AfterSwitchExpressions {
4
5 static String dayType(String day) {
6 return switch (day) {
7 case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "Weekday";
8 case "SATURDAY", "SUNDAY" -> "Weekend";
9 default -> "Unknown";
10 };
11 }
12
13 public static void main(String[] args) {
14 System.out.println(dayType("WEDNESDAY"));
15 System.out.println(dayType("SUNDAY"));
16 }
17}Output:
Weekday
Weekend
Both versions produce identical results, but the arrow form makes it structurally impossible to accidentally fall from one case into the next — a mistake covered in depth later in this article.
One sentence before the diagram: the colon form's cases are connected by fall-through unless a break interrupts it; the arrow form's cases are never connected to each other at all.
Colon form (old): Arrow form (new):
case "MONDAY": \ case "MONDAY", "TUESDAY" -> "Weekday";
case "TUESDAY": >-- falls through case "SATURDAY" -> "Weekend";
result = "Weekday"; (each arm is fully self-contained,
break; <-- easy to forget nothing to fall through into)
Syntax
Multiple Case Labels and yield
A single arrow-form case can list several values separated by commas, and a case whose logic needs more than one expression can use a block body with yield to produce its result.
1// File: YieldExample.java
2
3public class YieldExample {
4
5 static int quarterFromMonth(int month) {
6 return switch (month) {
7 case 1, 2, 3 -> 1;
8 case 4, 5, 6 -> 2;
9 case 7, 8, 9 -> 3;
10 case 10, 11, 12 -> {
11 int q = 4;
12 yield q;
13 }
14 default -> throw new IllegalArgumentException("Invalid month: " + month);
15 };
16 }
17
18 public static void main(String[] args) {
19 System.out.println(quarterFromMonth(2));
20 System.out.println(quarterFromMonth(11));
21 }
22}Output:
1
4
A single-expression arm, like case 1, 2, 3 -> 1;, needs no yield — the expression itself is the result. A block-bodied arm, like the one for months 10 through 12, must use yield to produce its value explicitly. A case can also throw instead of producing a value at all, which is valid since that arm never completes normally.
Exhaustiveness Over enum
Switching over an enum type lets the compiler verify every constant is covered, with no default required at all.
1// File: EnumSwitchExhaustiveExample.java
2
3public class EnumSwitchExhaustiveExample {
4
5 enum TrafficLight { RED, YELLOW, GREEN }
6
7 static String action(TrafficLight light) {
8 return switch (light) {
9 case RED -> "Stop";
10 case YELLOW -> "Slow down";
11 case GREEN -> "Go";
12 };
13 }
14
15 public static void main(String[] args) {
16 for (TrafficLight light : TrafficLight.values()) {
17 System.out.println(light + ": " + action(light));
18 }
19 }
20}Output:
RED: Stop
YELLOW: Slow down
GREEN: Go
Removing any one of the three cases here — without adding a default — would be a compile error, since the compiler can enumerate every possible TrafficLight value and knows immediately that a case is missing.
A guarded or partial switch over an enum still needs a default or full coverage to compile. Exhaustiveness is checked at compile time, never assumed at runtime.
Common Use Cases
Grouping several values into one outcome, as dayType's weekday and weekend cases show above, replaces the old pattern of stacking several colon-form labels with nothing between them.
Returning a value directly from a switch, without a variable declared outside the block, is the core benefit demonstrated in both the AfterSwitchExpressions and EnumSwitchExhaustiveExample examples above.
Multi-step computation within a single case, using a block body and yield, as the fourth case in YieldExample shows, covers logic too involved for a single expression.
Compiler-verified coverage over a fixed set of values, whether an enum as shown above or a sealed type as covered in this series' dedicated Pattern Matching article, catches a missing case at compile time instead of in production.
Real-World Example
A movie ticket pricing service prices each seat category differently, with a weekend surcharge that only applies to the recliner category — combining single-expression arms, a block-bodied arm with yield, and an enum switched over exhaustively.
1// File: SeatCategory.java
2
3public enum SeatCategory { ECONOMY, PREMIUM, RECLINER }1// File: TicketPricingService.java
2
3public class TicketPricingService {
4
5 public double priceFor(SeatCategory category, boolean isWeekend) {
6 return switch (category) {
7 case ECONOMY -> isWeekend ? 180.0 : 150.0;
8 case PREMIUM -> isWeekend ? 280.0 : 240.0;
9 case RECLINER -> {
10 double base = 450.0;
11 double surcharge = isWeekend ? 90.0 : 0.0;
12 yield base + surcharge;
13 }
14 };
15 }
16}1// File: TicketPricingDemo.java
2
3public class TicketPricingDemo {
4 public static void main(String[] args) {
5 TicketPricingService pricing = new TicketPricingService();
6
7 System.out.println("Economy weekday: " + pricing.priceFor(SeatCategory.ECONOMY, false));
8 System.out.println("Premium weekend: " + pricing.priceFor(SeatCategory.PREMIUM, true));
9 System.out.println("Recliner weekend: " + pricing.priceFor(SeatCategory.RECLINER, true));
10 System.out.println("Recliner weekday: " + pricing.priceFor(SeatCategory.RECLINER, false));
11 }
12}Output:
Economy weekday: 150.0
Premium weekend: 280.0
Recliner weekend: 540.0
Recliner weekday: 450.0
A mistake that appears often in fresher pull requests is adding a fourth seat category to an enum like SeatCategory without updating every switch that consumes it, and not noticing the gap until a specific category is requested in production. Because priceFor switches over the enum exhaustively with no default, the compiler itself would immediately flag TicketPricingService as needing an update the moment a new category is added — the missing case becomes a compile error, not a silent bug.
Combining Switch Expressions With Other Features
Pattern matching for switch, covered in full in this series' dedicated Pattern Matching article, builds directly on the arrow syntax and exhaustiveness rules introduced here, adding type patterns and record deconstruction on top. var combines naturally with a switch expression's result, since the result's type is usually already obvious from the cases themselves. Records pair well as a switch expression's return type when each case needs to produce more than one related value at once.
Best Practices
Prefer the arrow form over the colon form for any new switch, whether used as a statement or an expression, since it removes fall-through entirely and needs no break.
Group related values into one case label with commas, exactly as dayType and TicketPricingService do above, instead of writing a separate case for each value that shares the same outcome.
Let a switch expression over an enum stay exhaustive with no default, so the compiler catches a missing case the moment a new constant is added, rather than adding a default that would silently swallow it.
Keep block-bodied arms short — a yield after two or three lines of setup is fine, but a large block inside a single case is usually a sign the logic belongs in its own method.
Common Mistakes
Relying on the old colon-form switch statement without a break on every branch reintroduces the exact fall-through bug switch expressions exist to eliminate.
1// File: ColonFormFallThroughMistake.java
2
3public class ColonFormFallThroughMistake {
4
5 static String describe(int level) {
6 String result;
7 switch (level) {
8 case 1:
9 result = "Low";
10 case 2:
11 result = "Medium";
12 break;
13 default:
14 result = "Unknown";
15 }
16 return result;
17 }
18
19 public static void main(String[] args) {
20 System.out.println(describe(1));
21 System.out.println(describe(2));
22 }
23}Output:
Medium
Medium
describe(1) should intuitively return "Low", but the missing break after case 1: lets execution fall straight into case 2:, silently overwriting the result — exactly the class of bug the arrow form makes structurally impossible.
Forgetting that a block-bodied arrow arm must explicitly yield its value does not compile — falling off the end of the block with no yield leaves the switch expression with nothing to produce.
1// This does not compile - the block falls off the end with no
2// value produced
3static int quarterFromMonth(int month) {
4 return switch (month) {
5 case 1, 2, 3 -> 1;
6 default -> {
7 int q = 4;
8 }
9 };
10}Mixing arrow-form and colon-form case labels in the same switch block is not allowed at all — a single switch must use one style or the other throughout.
1// This does not compile - arrow and colon labels cannot be mixed
2// in the same switch block
3static String describe(int level) {
4 return switch (level) {
5 case 1 -> "Low";
6 case 2:
7 yield "Medium";
8 default -> "Unknown";
9 };
10}Interview Questions
Q1. What is the main problem with traditional switch statements that switch expressions solve?
Fall-through between cases when a break is missing, which switch expressions eliminate structurally in the arrow form — execution never continues into the next case regardless of whether break is written, because there is no break involved at all. Interviewers listen for "structurally impossible," not just "less likely" — the arrow form does not merely discourage the bug, it removes the mechanism that causes it.
Q2. What is the difference between the arrow (->) form and the colon (:) form of a switch case label?
The arrow form does not fall through between cases and needs no break; a single expression after -> is the case's result directly, or a block can yield one. The colon form still falls through exactly as it always has, even when used inside a switch expression, and requires yield to produce a value there instead of an implicit result. The nuance being tested is whether you know colon-form fall-through still exists inside a switch expression, which surprises many candidates.
Q3. What is the purpose of the yield keyword, and when is it required?
yield produces the value of a switch expression's arm. It is required inside any block-bodied arrow arm (-> { ... }) and inside any colon-form case used within a switch expression; a single-expression arrow arm does not need it, since the expression itself is the result.
Q4. Can a single switch case label match multiple values in Java 14+?
Yes, values can be comma-separated in one case label, such as case "SATURDAY", "SUNDAY" -> "Weekend";, replacing the older pattern of stacking several colon-form labels with no code between them to fall through into a shared branch.
Q5. Does a switch expression over an enum type require a default branch?
No, as long as every constant of the enum is covered by an explicit case — the compiler can enumerate all possible values of an enum and verify the switch is exhaustive without one. Product-based interviewers often ask what happens if a new constant is added later, checking whether you know it becomes a compile error rather than a silent runtime gap.
Q6. Can arrow-form and colon-form case labels be mixed in the same switch block?
No. A single switch block must use exclusively arrow-form labels or exclusively colon-form labels — mixing the two styles within one switch is a compile error.
Q7. Can a switch expression's arm body be a throw statement instead of a value?
Yes. An arm that should never normally complete, such as a case representing an invalid input, can throw an exception instead of producing a value — this is valid because that arm simply never completes normally, so there is nothing it needs to yield.
FAQs
Is switch expressions the same feature as pattern matching for switch?
No, though they are closely related. Switch expressions (Java 14) added the arrow syntax, yield, and value-based exhaustiveness for enum types. Pattern matching for switch (Java 21) later built on that same syntax to add type patterns and record deconstruction, covered in this series' dedicated Pattern Matching article.
Does the arrow form of switch still fall through between cases?
No. Each arrow-form case is a self-contained arm — execution never continues from one case into the next, regardless of how the case's body is written.
Can a switch expression be used as a statement, without assigning its result to anything?
Yes, the arrow form works equally well as a plain statement, such as switch (command) { case "start" -> start(); case "stop" -> stop(); }, with no return or assignment involved at all.
What happens if a switch expression over a non-enum, non-sealed type has no default and no case matches?
This situation cannot actually occur at runtime, because it is a compile-time requirement — a switch expression over any type the compiler cannot prove exhaustive on its own, such as int or String, must include a default case to compile in the first place.
Which Java version finalized switch expressions?
Java 14, via JEP 361, after an initial preview in Java 12 and a second preview with refinements in Java 13.
Can yield be used inside a colon-form case within a switch expression?
Yes. A switch expression can use colon-form labels instead of arrow labels, and in that style yield is what produces the expression's value — fall-through between colon-form cases still applies even though the overall switch is being used as an expression.
Is there a performance difference between an arrow-form switch and an old colon-form switch?
No. Both compile down to essentially the same bytecode dispatch mechanism — a tableswitch or lookupswitch instruction under the hood — so the choice between them affects readability and correctness, not runtime performance.
Summary
Switch expressions replace the traditional switch statement's biggest footgun — a missing break silently falling into the next case — with an arrow form that cannot fall through at all, while also letting a switch return a value directly instead of requiring a variable declared and assigned outside the block. Multiple values can share one case label, yield produces a result from a block-bodied arm, and switching over an enum gets compiler-verified exhaustiveness with no default needed.
The habit worth carrying forward from this article's ticket-pricing example is leaning on that exhaustiveness deliberately — letting a switch over an enum stay free of a default so the compiler itself flags the moment a new case needs handling, rather than discovering the gap in production.
What to Read Next
Learn Java's lightweight threads built for massive concurrency.