☕ Java

switch Expression

The switch expression (Java 14+) is a modern alternative to the switch statement that produces a value. It uses arrow labels (case value ->) that eliminate fall-through and the need for break statements. Multiple values can be combined in a single case. The yield keyword returns a value from a multi-statement case block. Switch expressions are exhaustive — the compiler verifies that all possible input values are covered.

Arrow Syntax and Basic Usage

Switch expressions use the arrow (->) syntax instead of colon (:). Each arrow case executes exactly one expression, block, or throw — there is no fall-through and no break needed. The entire switch expression produces a single value that can be assigned to a variable, returned from a method, or passed as a method argument.
Java
// ── Traditional switch statement (old): ─────────────────────────────
int day = 3;
String dayName;
switch (day) {
    case 1:  dayName = "Monday";    break;
    case 2:  dayName = "Tuesday";   break;
    case 3:  dayName = "Wednesday"; break;
    default: dayName = "Other";     break;
}

// ── Switch expression (Java 14+) — same logic, much cleaner: ─────────
String dayName2 = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3 -> "Wednesday";      // selected — day = 3
    case 4 -> "Thursday";
    case 5 -> "Friday";
    case 6 -> "Saturday";
    case 7 -> "Sunday";
    default -> "Invalid day";
};
System.out.println(dayName2);   // Wednesday

// ── Switch expression as method return value: ─────────────────────────
public static String getSeasonName(int month) {
    return switch (month) {
        case 12, 1, 2  -> "Winter";
        case 3, 4, 5   -> "Spring";
        case 6, 7, 8   -> "Summer";
        case 9, 10, 11 -> "Autumn";
        default -> throw new IllegalArgumentException(
            "Invalid month: " + month);
    };
}

// ── Switch expression as method argument: ─────────────────────────────
System.out.println(switch (day) {
    case 6, 7 -> "Weekend";
    default   -> "Weekday";
});

// ── Key differences from switch statement: ────────────────────────────
//
// Arrow labels (->):
//   • No fall-through — each case is completely isolated
//   • No break needed — arrow case runs exactly one thing
//   • Can be expression, block, or throw
//
// Multiple values per case (case 1, 2, 3 ->):
//   • Replaces stacked empty cases from fall-through pattern
//   • More readable and less error-prone
//
// Produces a value:
//   • Can be assigned, returned, or passed as argument
//   • The compiler verifies all cases return the same type
//
// Exhaustiveness:
//   • Compiler error if not all values are covered
//   • For enums: must cover all constants or have default
//   • For int/String: default is required

Multiple Case Labels

Switch expressions can group multiple values in a single case using comma separation. This replaces the fall-through stacking pattern from traditional switch statements with a clean, explicit syntax. Multiple labels mean exactly the same as separate cases — any of the listed values triggers that arm.
Java
// ── Multiple values with arrow case: ────────────────────────────────
int month = 4;

int daysInMonth = switch (month) {
    case 1, 3, 5, 7, 8, 10, 12 -> 31;   // all 31-day months in one line
    case 4, 6, 9, 11            -> 30;   // all 30-day months in one line
    case 2                      -> 28;   // February (simplified)
    default -> throw new IllegalArgumentException(
        "Month must be 1-12, got: " + month);
};
System.out.println("Days: " + daysInMonth);   // Days: 30

// ── Compare with old fall-through pattern: ────────────────────────────
// OLD (8 lines):
// case 1: case 3: case 5: case 7: case 8: case 10: case 12:
//     daysInMonth = 31; break;

// NEW (1 line):
// case 1, 3, 5, 7, 8, 10, 12 -> 31;

// ── String switch with multiple labels: ──────────────────────────────
String command = "quit";

switch (command.toLowerCase()) {
    case "start", "begin", "run" -> startService();
    case "stop", "halt"          -> stopService();
    case "quit", "exit", "bye"   -> {
        System.out.println("Goodbye.");
        System.exit(0);
    }
    default -> System.out.println("Unknown: " + command);
}

// ── Enum switch — multiple labels: ────────────────────────────────────
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

Day today = Day.SAT;

String type = switch (today) {
    case MON, TUE, WED, THU, FRI -> "Weekday";
    case SAT, SUN                 -> "Weekend";   // selected
};
System.out.println(today + " is a " + type);   // SAT is a Weekend

// ── Enum switch without default — compiler checks exhaustiveness: ─────
// If all enum constants are covered, default is NOT required.
// Adding a new enum constant without updating the switch
// will produce a compile error — a useful safety net.
int hoursWorked = switch (today) {
    case MON, TUE, WED, THU, FRI -> 8;
    case SAT                      -> 4;
    case SUN                      -> 0;
    // No default needed — all 7 Day constants are covered.
};

yield — Multi-Statement Cases

When a case requires more than a single expression — for example, local variable declarations, loops, or conditional logic — use a block body enclosed in braces. The yield keyword returns the value from the block, replacing what break did for values in earlier proposals. yield is mandatory in block cases; it is not used in arrow expression cases.
Java
// ── yield in a block case: ───────────────────────────────────────────
int score = 73;

String grade = switch (score / 10) {
    case 10, 9 -> "A";
    case 8     -> "B";
    case 7     -> {
        // Block body — can contain multiple statements:
        String base = "C";
        String modifier = (score >= 75) ? "+" : "";
        yield base + modifier;   // yield returns the value from this block
    }
    case 6 -> "D";
    default -> {
        if (score < 0 || score > 100) {
            throw new IllegalArgumentException(
                "Score out of range: " + score);
        }
        yield "F";
    }
};
System.out.println("Grade: " + grade);   // Grade: C

// ── yield vs return: ─────────────────────────────────────────────────
// yield  → returns a VALUE from a switch expression block case
// return → returns from the ENCLOSING METHOD
//
// Inside a switch expression block:
//   yield value;    ← correct — returns value from this case
//   return value;   ← returns from the method (if inside a method)

// ── Mixing arrow and block cases: ────────────────────────────────────
int statusCode = 404;
String message = switch (statusCode) {
    case 200 -> "OK";                                   // arrow — simple
    case 201 -> "Created";
    case 400 -> "Bad Request";
    case 404 -> {
        // Block — log and return value:
        System.err.println("Resource not found: " + statusCode);
        yield "Not Found";                              // yield in block
    }
    case 500 -> {
        System.err.println("Server error occurred");
        yield "Internal Server Error";
    }
    default -> {
        String msg = "Unexpected status: " + statusCode;
        System.err.println(msg);
        yield msg;
    }
};

// ── yield in traditional colon switch expression: ─────────────────────
// Switch expressions can also use colon (:) syntax — yield replaces break:
int val = 2;
int result = switch (val) {
    case 1:
        System.out.println("Processing 1");
        yield 100;        // yield instead of break
    case 2:
        System.out.println("Processing 2");
        yield 200;        // executes — yield 200
    default:
        yield -1;
};
System.out.println("Result: " + result);   // Result: 200

Exhaustiveness and the default Case

Switch expressions must be exhaustive — the compiler verifies that every possible value of the selector expression is covered. For enum types, covering all constants satisfies the compiler without a default. For int, String, and other types, a default case is required because the set of possible values is effectively infinite. Adding a new constant to an enum without updating all switch expressions that cover it individually produces a compile error.
Java
// ── Enum — exhaustive without default: ───────────────────────────────
enum Status { PENDING, ACTIVE, INACTIVE, SUSPENDED }

Status status = Status.ACTIVE;

String label = switch (status) {
    case PENDING   -> "Awaiting approval";
    case ACTIVE    -> "Running";              // selected
    case INACTIVE  -> "Stopped";
    case SUSPENDED -> "Temporarily halted";
    // No default — all 4 Status constants are covered.
    // Compiler error if a new constant is added to Status
    // without updating this switch.
};

// ── Adding default "just in case" for enum (defensive): ──────────────
String label2 = switch (status) {
    case PENDING   -> "Awaiting approval";
    case ACTIVE    -> "Running";
    case INACTIVE  -> "Stopped";
    case SUSPENDED -> "Temporarily halted";
    default -> throw new IllegalStateException(
        "Unhandled status: " + status);
    // If a new Status constant is added, this throws rather
    // than silently returning null or a wrong value.
};

// ── intdefault is required: ───────────────────────────────────────
int code = 2;
// COMPILE ERROR — missing default:
// String name = switch (code) {
//     case 1 -> "One";
//     case 2 -> "Two";
// };

// CORRECT — default covers all other int values:
String name = switch (code) {
    case 1  -> "One";
    case 2  -> "Two";         // selected
    case 3  -> "Three";
    default -> "Unknown: " + code;
};

// ── Stringdefault is required: ────────────────────────────────────
String input = "go";
String action = switch (input) {
    case "stop"  -> "Stopping";
    case "start" -> "Starting";
    case "go"    -> "Going";    // selected
    default      -> "Unknown command: " + input;
};

// ── Compile-time exhaustiveness check protects against refactoring: ───
// Scenario: team adds DELETED to the Status enum.
// All switch expressions covering all constants individually now
// produce a compile error at EVERY usage site — forcing developers
// to consciously handle the new constant rather than silently missing it.
// This is the main reason to avoid default in enum switches.

Pattern Matching in switch (Java 21+)

Java 21 extended switch expressions and statements with pattern matching. A case can now match by type (case Integer i), apply a guard condition (case Integer i when i > 0), and match null explicitly. This replaces chains of instanceof checks with a single expressive switch. The selector expression may be any reference type including Object.
Java
// ── Type patterns in switch (Java 21+): ─────────────────────────────
Object obj = 42;

String description = switch (obj) {
    case Integer i  -> "Integer: " + i;            // selected — obj is Integer
    case String  s  -> "String of length " + s.length();
    case Double  d  -> "Double: " + d;
    case int[]   ia -> "int array of length " + ia.length;
    case null       -> "null value";
    default         -> "Other type: " + obj.getClass().getName();
};
System.out.println(description);   // Integer: 42

// ── Guard conditions — when clause: ──────────────────────────────────
Object value = -5;

String category = switch (value) {
    case Integer i when i > 0    -> "Positive integer: " + i;
    case Integer i when i < 0    -> "Negative integer: " + i;  // selected
    case Integer i               -> "Zero";
    case String  s when !s.isBlank() -> "Non-blank string: " + s;
    case String  s               -> "Blank string";
    case null                    -> "Null";
    default                      -> "Other: " + value;
};
System.out.println(category);   // Negative integer: -5

// ── Replaces instanceof chains: ───────────────────────────────────────
// OLD — verbose instanceof chain:
public double calculateArea(Shape shape) {
    if (shape instanceof Circle c) {
        return Math.PI * c.radius() * c.radius();
    } else if (shape instanceof Rectangle r) {
        return r.width() * r.height();
    } else if (shape instanceof Triangle t) {
        return 0.5 * t.base() * t.height();
    } else {
        throw new IllegalArgumentException("Unknown shape");
    }
}

// NEW — pattern switch (Java 21+):
public double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle    c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle  t -> 0.5 * t.base() * t.height();
        default          -> throw new IllegalArgumentException(
            "Unknown shape: " + shape);
    };
}

// ── Sealed classes + pattern switch — exhaustive without default: ─────
// sealed interface Shape permits Circle, Rectangle, Triangle {}

public String describeShape(Shape shape) {
    return switch (shape) {
        case Circle    c -> "Circle r=" + c.radius();
        case Rectangle r -> "Rectangle " + r.width() + "x" + r.height();
        case Triangle  t -> "Triangle b=" + t.base();
        // No default needed — sealed hierarchy is exhaustive.
    };
}

switch Statement vs switch Expression — Comparison

Switch expressions were introduced to address the well-known problems of the traditional switch statement — accidental fall-through, verbose break statements, and the inability to produce a value directly. Understanding the differences helps choose the right form for each situation.
Java
// ── Side-by-side comparison: ─────────────────────────────────────────

// Traditional switch STATEMENT:
String result1;
switch (day) {
    case 1:
        result1 = "Monday";
        break;                      // break required
    case 2:
        result1 = "Tuesday";
        break;
    default:
        result1 = "Other";
        break;
}
// Problems:
// • Verbose — break on every case
// • Fall-through risk — missing break is silent bug
// • result1 must be declared before switch
// • Compiler cannot verify result1 is always assigned
//   (if default is omitted, result1 may be uninitialised)

// Modern switch EXPRESSION (Java 14+):
String result2 = switch (day) {
    case 1 -> "Monday";             // no break needed
    case 2 -> "Tuesday";
    default -> "Other";
};
// Advantages:
// • No fall-through — arrows are isolated
// • No break needed
// • Value assigned inline — no separate declaration
// • Compiler verifies exhaustiveness
// • All cases must produce a value of the same type

// ── When to use which: ────────────────────────────────────────────────
//
// Use switch EXPRESSION when:
//   Computing a value based on a selector
//   Returning from a method based on a selector
//   Replacing if-else-if chains that produce a value
//   Any new code targeting Java 14+
//
// Use switch STATEMENT when:
//   Executing side effects (print, update state, call void methods)
//   Intentional fall-through is needed (rare)
//   Maintaining Java 813 compatibility

// ── switch expression producing void (statement-like usage): ─────────
// Switch expressions can execute side effects too:
switch (command) {
    case "START" -> startService();   // void method — no value needed
    case "STOP"  -> stopService();
    default      -> System.out.println("Unknown: " + command);
}
// This is still a switch expression syntactically, but used
// as a statement (its value, if any, is discarded).

Related Topics in Control Statements