Switch Expressions

Reviewed & published by Brayan K

A switch expression is a modern form of switch that produces a value you can assign or return, uses arrow labels with no fall-through, and is checked by the compiler for exhaustiveness.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Before You Start

You should know the classic switch statement and enums. Switch expressions are standard from Java 14 onward.

What You'll Learn

The Big Idea — A Vending Machine, Not a To-Do List

💡 Analogy: The old switch statement is a to-do list: "if it's Monday, do these chores; remember to stop (break) or you'll keep doing the next day's chores too." The new switch expression is a vending machine: you put in a value, press the matching button (an arrow arm), and it hands you back exactly one product. No risk of accidentally dispensing every snack below the one you wanted.

That single returned value is the whole point: String role = switch (rank) { ... }; reads as one clear assignment instead of a mutable variable poked at by many case branches.

1️⃣ A switch That Returns a Value

Put the whole switch on the right of an assignment. Each arm uses case LABEL -> value;. The arrow makes each arm self-contained — there is no break, and no fall-through. Note the whole expression ends with a semicolon, just like any other assignment.

public class Main {
    public static void main(String[] args) {
        for (int day = 1; day <= 7; day++) {
            // switch as an EXPRESSION: it produces a value with arrow labels.
            String kind = switch (day) {
                case 1, 2, 3, 4, 5 -> "weekday";
                case 6, 7          -> "weekend";
                default            -> "invalid";
            };
            System.out.println("Day " + day + ": " + kind);
        }
    }
}

2️⃣ Block Arms, yield, and Exhaustive Enums

When an arm needs more than one statement, wrap it in braces and end with yield to supply the value — yield is the switch-expression equivalent of return. And when you switch over an enum and cover every constant, the switch is exhaustive, so you can drop default entirely.

public class Main {
    enum Coin { PENNY, NICKEL, DIME, QUARTER }

    public static void main(String[] args) {
        for (Coin c : Coin.values()) {
            // A block arm uses { } and 'yield' to return its value.
            int cents = switch (c) {
                case PENNY   -> 1;
                case NICKEL  -> 5;
                case DIME    -> 10;
                case QUARTER -> {
                    int base = 25;
                    yield base;          // 'yield' is the value of the block
                }
            };
            System.out.println(c + " = " + cents + " cents");
        }
        // No 'default' needed: the enum cases are exhaustive.
    }
}

3️⃣ Old Statement vs New Expression

Side by side, the difference is stark. The statement form needs a mutable label variable and a break after each case (forget one and you get a fall-through bug). The expression form assigns a value directly and can never fall through.

public class Main {
    public static void main(String[] args) {
        // OLD statement switch: easy to forget 'break' -> fall-through bugs.
        for (int n = 1; n <= 3; n++) {
            String label;
            switch (n) {
                case 1:
                    label = "one";
                    break;
                case 2:
                    label = "two";
                    break;
                default:
                    label = "many";
            }
            System.out.println("statement: " + n + " -> " + label);
        }

        // NEW expression switch: arrows never fall through, and it's a value.
        for (int n = 1; n <= 3; n++) {
            String label = switch (n) {
                case 1 -> "one";
                case 2 -> "two";
                default -> "many";
            };
            System.out.println("expression: " + n + " -> " + label);
        }
    }
}

4️⃣ Multiple Labels & default

Group values that behave the same with a comma-separated label: case "GET", "HEAD" -> 200;. For open-ended types like String or int you still need a default arm so the switch stays exhaustive.

public class Main {
    public static void main(String[] args) {
        String[] inputs = {"GET", "POST", "PUT", "DELETE", "TRACE"};
        for (String method : inputs) {
            // Multiple labels share an arm; a default keeps it exhaustive.
            int code = switch (method) {
                case "GET", "HEAD"        -> 200;
                case "POST", "PUT"        -> 201;
                case "DELETE"             -> 204;
                default                   -> 405;   // method not allowed
            };
            System.out.println(method + " -> " + code);
        }
    }
}

🧠 Quick Recall

Predict the result before opening each answer.

int x = switch (n) {
    case 1 -> 10;
    case 2 -> 20;
};

Answer: No — for an int input it is not exhaustive (what about 3, 4, ...?). error: the switch expression does not cover all possible input values. Add a default.

case 5 -> {
    int v = 5 * 5;
    break v;
}

Answer: A block arm must return its value with yield v;, not break v;. break doesn't produce a value in a switch expression.

String s = switch (day) {
    case TUE -> "two";
    case WED -> "three";
    default -> "?";
};

Answer: No. Arrow arms never fall through — TUE yields "two" and stops. Only the classic colon form (without break) falls through.

🎯 Your Turn — Days in a month

Use a switch expression with a multi-label case to map a month number to its day count.

public class Main {
    public static void main(String[] args) {
        // YOUR TURN: use a switch EXPRESSION to map a month number (1-12) to
        // its day count (ignore leap years). Group the 30-day months with a
        // multi-label case, give February 28, and let default handle 31.
        // Expected (first lines):
        //   Month 1: 31 days
        //   Month 2: 28 days
        //   Month 4: 30 days

        for (int month = 1; month <= 12; month++) {
            int days = 0; // replace 0 with: switch (month) { ... }
            System.out.println("Month " + month + ": " + days + " days");
        }
    }
}

🧩 Mini-Challenge — Tag log levels

Cover every enum constant (so no default) and use a block arm with yield for one of them.

public class Main {
    enum Level { DEBUG, INFO, WARN, ERROR }

    public static void main(String[] args) {
        // MINI-CHALLENGE: tag each log level. DEBUG and INFO share one arm
        // ("[normal] X"); WARN uses a { } block with yield ("[careful] X");
        // ERROR is "[ALERT] X". Because you cover all four enum constants,
        // you need NO default.
        //
        // Expected output:
        //   [normal] DEBUG
        //   [normal] INFO
        //   [careful] WARN
        //   [ALERT] ERROR

        for (Level lv : Level.values()) {
            String tag = ""; // replace with: switch (lv) { ... }
            System.out.println(tag);
        }
    }
}

Common Errors

Pro Tips

📋 Quick Reference

ConceptSyntaxNote
Expression formvar x = switch (k) {...};Produces a value
Arrow armcase 1 -> "one";No break, no fall-through
Multiple labelscase 1, 2, 3 -> ...Share one arm
Block armcase X -> { ... yield v; }yield gives the value
Defaultdefault -> ...;Catches everything else
Enum exhaustiveall constants covereddefault optional
Colon form valuecase 1: yield x;yield, not break
Switch on Stringswitch (s) {...}Needs default
As a returnreturn switch (k) {...};Common pattern
Statement formswitch (k) { case 1 -> act(); }Arrows, no value
SinceJava 14JEP 361

📚 Resources / Keep Learning

🎉 Lesson Complete!

You can now write a switch that returns a value, use arrow arms that never fall through, supply values from block arms with yield, group cases with multiple labels, and rely on exhaustiveness so an enum switch needs no default.

Practice quiz

What is the key difference between a switch expression and a switch statement?

  • Expressions can only use int
  • Statements are faster
  • A switch expression produces a value you can assign
  • Expressions require break

Answer: A switch expression produces a value you can assign. A switch expression evaluates to a value, so you can write var x = switch(...) {...};.

What separator do arrow-style switch labels use?

  • -> (arrow)
  • : (colon)
  • => (fat arrow)
  • | (pipe)

Answer: -> (arrow). Arrow labels use ->; the older statement form uses a colon with break.

Do arrow-style switch arms fall through to the next case?

  • Yes, like the old switch
  • Only without break
  • Only for default
  • No — each arrow arm runs exactly its own code

Answer: No — each arrow arm runs exactly its own code. Arrow arms never fall through, eliminating the classic missing-break bug.

How do you return a value from a multi-statement { } arm?

  • return
  • yield value
  • break value
  • = value

Answer: yield value. Inside a block arm you use yield to supply the arm's value.

How do you match several values in one arm?

  • Comma-separate the labels: case 1, 2, 3 ->
  • Repeat the case keyword
  • Use ||
  • Use a range a..b

Answer: Comma-separate the labels: case 1, 2, 3 ->. List the constants separated by commas in a single label: case 1, 2, 3 -> ...

When is a default branch NOT required in a switch expression?

  • Never required
  • Only for int
  • When switching over an enum and all constants are covered
  • Only for String

Answer: When switching over an enum and all constants are covered. If every enum constant has an arm, the switch is exhaustive and default is optional.

In which Java version did switch expressions become standard?

  • Java 8
  • Java 14
  • Java 12
  • Java 17

Answer: Java 14. Switch expressions previewed in 12/13 and were finalized in Java 14 (JEP 361).

What happens if a switch expression is not exhaustive?

  • It returns null
  • It throws at runtime
  • It returns 0
  • It is a compile error

Answer: It is a compile error. A switch expression must cover every possible value; otherwise the compiler rejects it.

Can you still use colon-and-yield together in a switch expression?

  • No
  • Yes — case 1: yield x; is valid, just without arrows
  • Only in Java 21
  • Only with enums

Answer: Yes — case 1: yield x; is valid, just without arrows. The colon form of a switch expression uses yield instead of break to produce a value.

Why is the arrow switch considered safer than the classic one?

  • It is faster
  • It allows null keys
  • No fall-through and the compiler enforces exhaustiveness
  • It supports more types

Answer: No fall-through and the compiler enforces exhaustiveness. No accidental fall-through plus required exhaustiveness catches whole classes of bugs at compile time.

Continue this course

Frequently asked questions

What makes a switch an expression rather than a statement?

A switch expression evaluates to a single value that you can assign, return, or pass as an argument: var size = switch (s) { ... };. A switch statement, by contrast, just performs actions and produces no value. The expression form was finalized in Java 14. You can spot it because the whole switch sits on the right-hand side of an assignment (or as a return value) and ends with a semicolon.

What is the difference between -> arms and yield?

An arrow arm whose right side is a single expression simply uses that expression as the value: case 1 -> "one";. When an arm needs several statements, you wrap them in braces and use yield to hand back the result: case 2 -> { var t = compute(); yield t; }. yield is to a switch expression what return is to a method — it supplies the value. You only need yield for multi-statement block arms.

Why don't arrow switch arms fall through?

In the classic colon-style switch, control 'falls through' to the next case unless you write break, which caused countless bugs. Arrow-style arms run exactly the code to the right of the arrow and then the switch is done — there is no fall-through at all. If you genuinely want two values to behave identically, you list them in one label: case 1, 2 -> ...; rather than relying on fall-through.

When do I need a default branch?

A switch expression must be exhaustive: every possible input value must be handled, because the expression has to produce a value no matter what. For an int or String you almost always need a default to cover 'everything else'. But when you switch over an enum and provide an arm for every constant, the compiler knows all cases are covered, so default is optional (and adding one can even hide the helpful error you'd get if someone adds a new enum constant later).

Can I mix multiple labels and blocks?

Yes. You can combine comma-separated labels with either single-expression or block arms freely. For example: case MON, TUE, WED, THU, FRI -> "work"; and case SAT, SUN -> { log("weekend"); yield "rest"; }. Multiple labels keep related cases together without duplicating the body, and block arms let those grouped cases run several statements before yielding a value.

Related lessons