Skip to content
Java modern java 6 min read

Switch Expressions

Switch expressions, made permanent in Java 14, transform the classic switch statement into something far more powerful: a concise, expression-oriented construct that returns a value, eliminates fall-through bugs, and works beautifully with pattern matching in Java 21.

Why Switch Needed an Upgrade

The traditional switch statement has three well-known pain points:

  • Fall-through by default — forgetting a break silently executes the next case.
  • No return value — you must assign a variable inside each case, duplicating logic.
  • Verbose — even simple lookups need many lines.

Switch expressions fix all three.

Arrow-Case Syntax (the Basics)

The new -> form executes exactly one branch and never falls through.

public class DayType {
    public static void main(String[] args) {
        String day = "SATURDAY";

        String type = switch (day) {
            case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "Weekday";
            case "SATURDAY", "SUNDAY" -> "Weekend";
            default -> "Unknown";
        };

        System.out.println(day + " is a " + type);
    }
}

Output:

SATURDAY is a Weekend

Key points:

  • The whole switch is an expression — it produces a value assigned to type.
  • Multiple labels share a single case: case "SATURDAY", "SUNDAY" ->.
  • No break is needed; each arrow branch is exactly one value or block.

Using Blocks with yield

When a branch needs more than a single expression, wrap it in {} and use yield to return the value.

public class Discount {
    public static void main(String[] args) {
        String membership = "GOLD";

        double discount = switch (membership) {
            case "BRONZE" -> 0.05;
            case "SILVER" -> 0.10;
            case "GOLD"   -> {
                System.out.println("Applying premium discount...");
                yield 0.20;  // yield exits the block and provides the value
            }
            default -> 0.0;
        };

        System.out.printf("Discount: %.0f%%%n", discount * 100);
    }
}

Output:

Applying premium discount...
Discount: 20%

Note: yield is only valid inside a switch expression block. It is NOT a general return statement — it applies exclusively to the surrounding switch expression.

Exhaustiveness

Because a switch expression must always produce a value, the compiler checks that every possible input is covered. For String, int, and other non-sealed types you need a default case. For enum types, you can either list all constants OR add default.

enum Season { SPRING, SUMMER, AUTUMN, WINTER }

public class SeasonLabel {
    public static void main(String[] args) {
        Season s = Season.SUMMER;

        // No default needed — all four constants are listed
        String label = switch (s) {
            case SPRING -> "Blooming";
            case SUMMER -> "Hot";
            case AUTUMN -> "Falling";
            case WINTER -> "Freezing";
        };

        System.out.println(label);
    }
}

Output:

Hot

Tip: If you add a new constant to the enum later, the compiler will report a compile-time error on any exhaustive switch that doesn’t handle it — a safety net you don’t get with if-else chains.

Mixing with Traditional Colon-Case Syntax

You can still use the old case X: style inside a switch expression, but you must use yield (not return) to supply the value, and fall-through still applies between colon cases.

int code = 2;

String message = switch (code) {
    case 1:
    case 2:
        yield "Low priority";   // covers both 1 and 2 via fall-through
    case 3:
        yield "High priority";
    default:
        yield "Unknown";
};

Warning: Mixing arrow and colon cases in the same switch is not allowed. Choose one style per switch.

Switch Expressions vs Switch Statements

FeatureStatement (switch)Expression (switch)
Returns a valueNoYes
Fall-through defaultYesNo (with ->)
Exhaustiveness checkNoYes (for expressions)
break to exitRequired (colon style)Not needed (arrow style)
yield keywordNot applicableUsed in block arms
Available sinceJava 1.0Java 14 (permanent)

Pattern Matching in Switch (Java 21)

Java 21 elevated switch expressions even further with full pattern matching (JEP 441). You can now test the type of the selector in each case — and even add when guards.

public class Shape {
    sealed interface Figure permits Circle, Rectangle, Triangle {}
    record Circle(double radius) implements Figure {}
    record Rectangle(double w, double h) implements Figure {}
    record Triangle(double base, double height) implements Figure {}

    static double area(Figure fig) {
        return switch (fig) {
            case Circle c    -> Math.PI * c.radius() * c.radius();
            case Rectangle r -> r.w() * r.h();
            case Triangle t  -> 0.5 * t.base() * t.height();
        };
    }

    public static void main(String[] args) {
        System.out.printf("Circle area: %.2f%n", area(new Circle(5)));
        System.out.printf("Rect area:   %.2f%n", area(new Rectangle(4, 6)));
    }
}

Output:

Circle area: 78.54
Rect area:   24.00

Because Figure is a sealed class, the compiler knows the complete list of subtypes and no default is needed. Add a new Figure subtype and this switch immediately fails to compile — intentional exhaustiveness.

Guarded Patterns with when

static String classify(Object obj) {
    return switch (obj) {
        case Integer i when i < 0  -> "Negative integer";
        case Integer i when i == 0 -> "Zero";
        case Integer i             -> "Positive integer: " + i;
        case String s  when s.isBlank() -> "Blank string";
        case String s              -> "String: " + s;
        case null                  -> "Null value";
        default                    -> "Other: " + obj.getClass().getSimpleName();
    };
}

public static void main(String[] args) {
    System.out.println(classify(-3));
    System.out.println(classify(0));
    System.out.println(classify(42));
    System.out.println(classify("hello"));
}

Output:

Negative integer
Zero
Positive integer: 42
String: hello

Note: The when guard is evaluated only after the type pattern matches — it refines, not replaces, the type test.

Null Handling in Switch (Java 21)

Before Java 21, passing null to a switch always threw a NullPointerException. Now you can match null explicitly with case null.

static void greet(String name) {
    switch (name) {
        case null   -> System.out.println("No name provided");
        case "Java" -> System.out.println("Hello, Java!");
        default     -> System.out.println("Hello, " + name + "!");
    }
}

Under the Hood

At the bytecode level, switch expressions compile down to the same tableswitch or lookupswitch JVM instructions used by traditional switch statements — so there is no runtime overhead for choosing the newer syntax.

The compiler enforces exhaustiveness at compile time, not runtime. For pattern-matching switches over sealed types, the compiler builds a complete subtype graph and verifies every leaf is handled. If not, you get a compile error rather than a surprise MatchException at runtime.

yield compiles to a local variable assignment followed by a jump to the end of the switch, similar to what you would write manually with the old statement style. It is purely a source-level construct; there is no special yield bytecode instruction.

Arrow cases (->) generate code that unconditionally jumps past subsequent cases, eliminating fall-through at the bytecode level — not just at the source level.

Version Timeline

Java VersionWhat Changed
Java 12Switch expressions introduced as a preview feature
Java 13yield replaced break value in preview
Java 14Switch expressions became standard (permanent)
Java 16Pattern matching for instanceof (prelim for switch patterns)
Java 17Pattern matching for switch added as preview
Java 21Pattern matching for switch became standard (JEP 441)
  • switch Statement — the classic statement form and how it differs from expressions
  • Pattern Matchinginstanceof pattern matching and how it pairs with switch
  • Sealed Classes — seal a type hierarchy to get exhaustive switch coverage
  • Records — combine records with pattern-matching switch for clean data processing
  • Enums — enums work especially well with switch expressions and exhaustiveness checks
  • Modern Java (9–21) — overview of all modern Java features in context
Last updated June 13, 2026
Was this helpful?