Command Palette

Search for a command to run...

PHASE 2Beginner Java 14+ ~31 min· topic 4 of 10

Topic 2.4

Switch Expressions and Arrow Labels

In one line

Since Java 14, switch can be an expression that produces a value, and case X -> arrow labels never fall through. The compiler checks that a switch expression covers every possible input, and Java 21 adds pattern matching, guards and case null on top.

Think of it like this

A ticket machine at a museum. You press 'child', 'adult' or 'senior' and it hands you back exactly one price. It never gives you two tickets by accident, and if someone added a new button without a price, the machine wouldn't be allowed to ship. A switch expression is that machine: one input, exactly one answer, every option covered.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Switch expression
A switch that produces a value you can assign or return, like int x = switch (...) { ... };.
Arrow label
A case X -> label. Only the code to its right runs; it never falls through.
yield
A statement inside a switch-expression block that says 'this is the switch's value'. It's a contextual keyword, so it's only special inside a switch expression.
Exhaustive
Covering every possible input. The compiler refuses a switch expression that might find no matching case.
Pattern
A shape to test a value against, like 'is an Integer'. If it matches, the value is also given a name you can use.
Guard
An extra when condition on a pattern case, like case Integer i when i > 0.
Sealed type
A class or interface that lists every class allowed to extend it, so the compiler knows all the possibilities (Java 17, Phase 5).

Step by step

01Arrow labels: no fall-through, no break

Replace case 1: with case 1 ->. The arrow form runs exactly the one expression, block or throw on its right, and then leaves the switch. You can't forget a break, because there's nothing to forget.

Group values with commas. This is a switch statement (it prints, it doesn't produce a value), written with arrows.

Main.javawhole filejava
int day = 6;
switch (day) {
    case 1, 2, 3, 4, 5 -> System.out.println("Weekday");
    case 6, 7          -> System.out.println("Weekend");
    default            -> System.out.println("Not a day");
}
terminal
$ java Main.java
── expected output ──
Weekend

02A switch that returns a value

Put the switch on the right of =. Each arm's expression becomes the value. The variable gets assigned in one place, it can be final, and the compiler checks every path gives a value, so no 'might not have been initialized' errors.

Compare this with Topic 2.3: no breaks, no declared-but-unassigned variable, no fall-through risk.

Main.javawhole filejava
int day = 3;
String name = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3 -> "Wednesday";
    default -> "Unknown";
};                       // <- semicolon: this is one assignment statement
System.out.println(name);   // Wednesday

03Blocks and yield

When an arm needs more than one expression, give it a block in braces. Inside, yield hands back the value. Think of yield as return, but for the switch instead of the method.

Each block must end by yielding a value or throwing. If control can reach the closing brace without either, javac says switch rule completes without providing a value.

Main.javawhole filejava
int score = 72;
String band = switch (score / 10) {
    case 10, 9 -> "A";
    case 8     -> "B";
    case 7     -> {
        String base = "C";
        yield score % 10 >= 5 ? base + "+" : base;   // 72 -> "C"
    }
    default    -> "below C";
};

04Exhaustiveness: the compiler checks every case

A switch expression must produce a value for every input. With an int selector there are four billion possible values, so you need default. With an enum, covering every constant is enough.

This is the main reason to prefer switch expressions for enums. Add THU to the enum later, and every switch expression that doesn't handle it fails to compile, pointing you to each place that needs updating. A classic switch statement would silently do nothing for THU.

Main.javawhole filejava
enum Day { MON, TUE, WED }

Day d = Day.TUE;
String type = switch (d) {
    case MON -> "start";
    case TUE -> "middle";
    // WED missing -> compile error
};
terminal
$ javac Main.java
── expected output ──
Main.java:5: error: the switch expression does not cover all possible input values
String type = switch (d) {
^
1 error

05Colon labels and yield, and the one rule about mixing

A switch expression may also use the old colon labels with yield. Fall-through comes back with them: below, code = 2 prints two, falls into case 3, and yields 30.

You must pick one style per switch. Mixing case 1: and case 2 -> in the same switch is error: different case kinds used in the switch. In practice, use arrows for new code.

Main.javawhole filejava
int code = 2;
int x = switch (code) {
    case 1: yield 10;
    case 2: System.out.println("two");   // falls through
    case 3: yield 30;
    default: yield 0;
};
System.out.println(x);   // two, then 30

06Java 21: patterns, guards and case null

From Java 21 the selector can be any reference type, and a case can test the value's type and name it at the same time: case Integer i -> means 'if it's an Integer, call it i'. A when guard adds a condition.

case null handles null explicitly instead of throwing. Cases are checked top to bottom, and the compiler rejects a case that can never match because an earlier, broader case already covers it (a dominated label).

Main.javawhole filejava
static String describe(Object o) {
    return switch (o) {
        case null -> "nothing";
        case Integer i when i > 100 -> "big number " + i;
        case Integer i -> "number " + i;
        case String s -> "text of length " + s.length();
        default -> "something else";
    };
}
Java 21: patterns, guards and case nulldiagram
Rendering diagram…

07What the compiler adds for you

When you cover every enum constant without default, the compiler still adds a hidden default arm. It only runs if the enum changed after your code was compiled (a new constant in a library jar). It throws IncompatibleClassChangeError for code compiled for Java 14–20 and MatchException for code compiled for Java 21 and later, rather than silently returning nothing.

Switch expressions on int, String and enums compile to the same tableswitch/lookupswitch bytecode as Topic 2.3. Pattern switches (Java 21) compile to an invokedynamic call to java.lang.runtime.SwitchBootstraps.typeSwitch, which works out the matching case index at run time.

Try it yourself

  1. 1

    Break exhaustiveness on purpose

    In 'Exhaustive enum switch', add JUPITER to the enum. Compile and read the error. Then add case JUPITER -> "giant"; and run again.

  2. 2

    Turn a statement into an expression

    Take the first practice answer from Topic 2.3 (days in a month) and rewrite daysIn as return switch (month) { ... }; with arrow labels. Count how many lines disappear.

  3. 3

    Reorder pattern cases

    In 'Pattern matching for switch' (needs Java 21), move case Integer i -> above case Integer i when i > 100 ->. Compile. The error this case label is dominated by a preceding case label tells you the guarded case could never run.

Code & diagrams

Arrow labels in a switch statement New tab
Sign in to run this example in your browser.

Expected output

1: weekday
6: weekend
9: not a day
Switch expressions, blocks and yield New tab
Sign in to run this example in your browser.

Expected output

100 -> A
84 -> B
77 -> C+
72 -> C
40 -> below C
Size M costs 3
Exhaustive enum switch, no default needed New tab

Add `JUPITER` to the enum and this no longer compiles until you handle it. That is the point.

Sign in to run this example in your browser.

Expected output

MERCURY: inner, hot
VENUS: inner, hot
EARTH: home
MARS: red
Pattern matching for switch Java 21+ New tab
This example needs Java 21+. The in-browser compiler is Java 17: install JDK 21 or newer and run it with "java Main.java".

Expected output

nothing
big number 500
number 7
empty text
text of length 5
something else: Double
Old vs new, side by sidejava

Both compute the same value. The new form can't fall through, assigns once, and is checked for exhaustiveness.

// Classic statement (Topic 2.3)
String name;
switch (day) {
    case 1: name = "Mon"; break;
    case 2: name = "Tue"; break;
    default: name = "?";
}

// Switch expression (Java 14+)
String name2 = switch (day) {
    case 1 -> "Mon";
    case 2 -> "Tue";
    default -> "?";
};

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Leave out a case in a switch expression

Write a switch expression on an int with case 1 and case 2 but no default.

Main.javawhole filejava
public class Main {
    public static void main(String[] args) {
        int d = 9;
        String n = switch (d) {
            case 1 -> "Mon";
            case 2 -> "Tue";
        };
        System.out.println(n);
    }
}
terminal
$ javac Main.java
── what you'll see ──
Main.java:4: error: the switch expression does not cover all possible input values
String n = switch (d) {
^
1 error

Break #2

Forget yield in a block

Give an arm a block that computes a value but never yields it.

Main.javawhole filejava
public class Main {
    public static void main(String[] args) {
        int d = 3;
        String n = switch (d) {
            case 1 -> "Mon";
            default -> {
                String t = "Day " + d;
            }
        };
        System.out.println(n);
    }
}
terminal
$ javac Main.java
── what you'll see ──
Main.java:8: error: switch rule completes without providing a value
}
^
(switch rules in switch expressions must either provide a value or throw)
1 error

Break #3

Mix colon and arrow labels

Write case 1: ... break; and case 2 -> ... in the same switch.

terminal
$ javac Main.java
── what you'll see ──
Main.java:6: error: different case kinds used in the switch
case 2 -> System.out.println("b");
^
1 error

Myth vs fact

Myth

Arrow labels only work in switch expressions.

Fact

They work in switch statements too, and remove fall-through there as well.

Myth

yield is a reserved keyword everywhere.

Fact

It's a contextual keyword: special only as a statement inside a switch expression. (Since Java 14 you can't call a method named yield without qualifying it, though.)

Myth

An enum switch expression always needs a default.

Fact

Covering every constant makes it exhaustive. Leaving out default is better: the compiler then flags new constants.

Myth

Switch expressions are slower than the old switch.

Fact

For int, String and enum selectors they compile to the same tableswitch/lookupswitch bytecode.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Switch expressions arrived as a preview in Java 12 and 13 (JEP 325, JEP 354; break value in 12 became yield in 13) and became standard in Java 14 (JEP 361). Pattern matching for switch was previewed in 17–20 and finalised in Java 21 (JEP 441), together with record patterns (JEP 440).

  • ▸

    Exhaustive switches without default get a synthetic default that throws. JEP 441 changed it from IncompatibleClassChangeError to MatchException for code compiled with the Java 21 rules. It's a separate-compilation safety net, not something your tests will normally hit.

  • ▸

    Pattern switches compile to invokedynamic bootstrapped by SwitchBootstraps.typeSwitch (or enumSwitch), which returns the index of the first matching label; guards are re-checked in a loop that restarts the search from the next index when a guard fails.

  • ▸

    Exhaustiveness plus sealed interfaces gives you algebraic data types: sealed interface Shape permits Circle, Square and a switch over Shape with no default fails to compile when a new shape is added. It is the modern Java replacement for much of the Visitor pattern (see the System Design course's design patterns).

Remember this

  1. 1

    An arrow label case 1 -> ... runs only the code on its right: one expression, one block in braces, or a throw. There is no fall-through and no break needed. Several values share one arrow with commas: case SAT, SUN -> "weekend". Arrow labels work in switch statements too, not only expressions.

  2. 2

    A switch expression produces a value: String name = switch (day) { case 1 -> "Mon"; ... };. Note the semicolon after the closing brace: the whole thing is part of an assignment statement.

  3. 3

    When an arm needs several statements, use a block and finish it with yield value;. yield means 'this is the value of the switch'. return can't be used for this (it would leave the whole method), and break out of a switch expression is a compile error.

  4. 4

    A switch expression must be exhaustive: every possible selector value must be handled. For int or String that means a default. For an enum, listing every constant is enough, with no default. If someone later adds a constant, the code stops compiling instead of silently doing nothing, which is the biggest safety win of this feature.

  5. 5

    Colon labels still work inside a switch expression (case 1: yield 10;) and still fall through, but you can't mix : and -> labels in one switch (different case kinds used in the switch).

  6. 6

    Java 21 (JEP 441) adds pattern matching for switch: any reference type as a selector, type patterns (case Integer i ->), guards (case Integer i when i > 100 ->), case null, and exhaustiveness checks over sealed hierarchies (Phase 5). You'll use these heavily with records later in the course.

Explain it without notes

01

What are the differences between case X: and case X ->?

02

Why can't you use return to give a switch expression its value, and what do you use instead?

03

What does exhaustive mean for a switch expression, and why is it better to list every enum constant than to add a default?

04

What did Java 21 add to switch?

Practice

01

Rewrite the month-length switch from Topic 2.3 as a switch expression with arrow labels, and print the lengths for months 2, 4 and 12.

02

Write a switch expression that maps a traffic light enum (RED, YELLOW, GREEN) to the seconds until change (30, 5, 25), with no default. Print all three.

03

Using a switch expression on a String command ("add", "sub", "mul") compute the result for a = 6 and b = 3; for anything else, yield 0 from a block that also prints unknown command. Test with "mul" and "pow".

Trade-offs

  • ↔

    Switch expressions need Java 14+; older codebases (Java 8 and 11 are still common) can only use the classic statement.

  • ↔

    Leaving out default on an enum switch gives compile-time safety when constants are added, but a library that adds constants can then break callers that compiled against the old version (they get MatchException at run time).

  • ↔

    Pattern switches replace long instanceof chains neatly, but overusing them on open class hierarchies scatters behaviour; for types you own and extend often, polymorphism (Phase 5) may fit better.

Done when you can

  • Done when you can write a switch expression with arrow labels, a block arm and yield.

  • Done when you can explain why a switch expression must be exhaustive and fix the error when it isn't.

  • Done when you choose to omit default on enum switch expressions and can say why.

  • Done when you can read a Java 21 pattern switch with guards and case null.

  • Done when you can convert any classic switch statement to the arrow form without changing its behaviour.