☕ 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

while Loop
The while loop is a condition-controlled loop — it continues executing as long as its boolean condition remains true, without specifying in advance how many iterations will occur. It is the natural choice for reading input until end-of-stream, polling until a condition changes, retrying an operation, or any scenario where the termination condition depends on data or events that cannot be predetermined. This entry covers syntax, execution flow, common patterns, infinite loops with controlled exit, and the differences between while and for.
do-while Loop
The do-while loop is Java's only post-test loop — the condition is evaluated after the body executes, which guarantees the body runs at least once regardless of the condition's initial value. This property makes do-while the natural fit for scenarios where the first execution must happen before the first check: menu-driven programs, input validation, digest computation, and game turns. This entry covers syntax, when to use do-while over while, input validation patterns, and the subtle differences that matter in practice.
Enhanced for Loop
The enhanced for loop — also called the for-each loop — was introduced in Java 5 to provide a clean, readable syntax for iterating over arrays and any class that implements Iterable. It eliminates index management and iterator boilerplate entirely. The developer declares what each element is and what to do with it, without concerning themselves with how the iteration mechanism works. This entry covers syntax, the Iterable contract, limitations compared to the indexed loop, modification restrictions, and how for-each maps to iterators under the hood.
break Statement
The break statement immediately terminates the nearest enclosing loop or switch block and transfers control to the first statement after that block. It is one of Java's three jump statements alongside continue and return. break is essential for exiting loops early when a search condition is met, preventing infinite loops from running forever, and stopping switch execution after a matched case.