Java Switch Expressions (Java 14+)

Switch expressions, standardized in Java 14 (JEP 361), let switch produce a value directly. Combined with arrow-case syntax, they eliminate fall-through bugs, reduce boilerplate, and integrate naturally with pattern matching introduced in later Java versions.

Contents

Arrow-case Syntax

The arrow -> replaces the colon :. Arrow cases do not fall through, so break is never needed. Each arrow case can be a single expression, a block, or a throw.

Java
// Old style — fall-through prone, verbose
int numLetters;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        numLetters = 6;
        break;
    case TUESDAY:
        numLetters = 7;
        break;
    case THURSDAY:
    case SATURDAY:
        numLetters = 8;
        break;
    case WEDNESDAY:
        numLetters = 9;
        break;
    default:
        throw new IllegalArgumentException("Unknown day: " + day);
}

// New style — switch expression with arrow syntax
int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
};
// No default needed: Day is an enum — compiler checks exhaustiveness
Arrow cases never fall through. If you need fall-through behavior for legacy reasons, stick to colon-style cases, but prefer arrow syntax for all new code.

Returning Values with yield

When a case body is a block (not a single expression), use yield to return the value from the block. yield is a context-sensitive keyword: it only has meaning inside a switch expression block.

Java
int result = switch (input) {
    case "one"   -> 1;
    case "two"   -> 2;
    case "three" -> 3;
    default -> {
        // block body — must use yield to produce the value
        System.out.println("Unknown input: " + input);
        yield -1;
    }
};

// yield also works with colon-style cases (traditional syntax)
int score = switch (grade) {
    case 'A':
        System.out.println("Excellent!");
        yield 100;
    case 'B':
        yield 85;
    case 'C':
        yield 70;
    default:
        yield 0;
};
yield is only valid inside a switch expression, not a switch statement. A switch expression MUST yield a value from every execution path (the compiler enforces this).

Exhaustiveness

A switch expression (one that produces a value) must cover all possible values. For enums, the compiler checks that every constant is handled. For other types, a default or case null, default is required.

Java
enum Season { SPRING, SUMMER, AUTUMN, WINTER }

// Exhaustive — all 4 enum constants are covered, no default needed
String activity = switch (season) {
    case SPRING -> "Gardening";
    case SUMMER -> "Swimming";
    case AUTUMN -> "Hiking";
    case WINTER -> "Skiing";
};

// If you add a new enum constant (e.g., MONSOON) without updating
// the switch, it becomes a compile-time error — not a silent runtime bug.

// For String/int selectors — default is required
String category = switch (statusCode) {
    case 200, 201 -> "Success";
    case 400      -> "Bad Request";
    case 401, 403 -> "Auth Error";
    case 404      -> "Not Found";
    case 500      -> "Server Error";
    default       -> "Unknown (" + statusCode + ")";
};

Multiple Labels per Case

Multiple case labels can be combined on a single case using commas, both in arrow and traditional syntax:

Java
// Multiple constants → same result
String type = switch (ch) {
    case 'a', 'e', 'i', 'o', 'u',
         'A', 'E', 'I', 'O', 'U' -> "vowel";
    default                       -> "consonant";
};

// Switch expression used directly as a method argument
System.out.println(switch (month) {
    case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER -> 31;
    case APRIL, JUNE, SEPTEMBER, NOVEMBER                     -> 30;
    case FEBRUARY -> year % 4 == 0 ? 29 : 28;
});

Practical Examples

Switch expressions are most valuable for mapping a finite set of inputs to output values: enum constants to labels, status codes to categories, type tokens to behavior. The arrow form (case X -> result) is preferred for single-expression cases because it removes fall-through entirely and keeps each arm on one line. When a case requires multiple statements, use a block with yield to return the value. Combined with sealed classes, switch expressions become exhaustive type dispatch: the compiler verifies every subtype is handled and raises a compile error if you add a new subtype without updating the switch.

Java
// HTTP status classifier
record HttpStatus(int code, String phrase) {}

String classify(int code) {
    return switch (code / 100) {
        case 1 -> "Informational";
        case 2 -> "Success";
        case 3 -> "Redirection";
        case 4 -> "Client Error";
        case 5 -> "Server Error";
        default -> "Unknown";
    };
}

// Switch expression assigning to a var
var description = switch (shape) {
    case Circle c    -> "Circle with radius " + c.radius();
    case Rectangle r -> "Rectangle " + r.width() + "x" + r.height();
    default          -> "Unknown shape";
};

// Nested switch expressions
String priority = switch (severity) {
    case CRITICAL -> switch (environment) {
        case PRODUCTION -> "P0 - Page immediately";
        case STAGING    -> "P1 - Fix before release";
        default         -> "P2 - Fix soon";
    };
    case HIGH    -> "P2 - Fix soon";
    case MEDIUM  -> "P3 - Schedule";
    case LOW     -> "P4 - Backlog";
};
Switch expressions can be used anywhere a value is expected, as a method argument, in a variable initializer, in a return statement, or even as the initializer of another switch.