Back to Java 21+ Features
Q9Question 9 of 10

What are switch expressions?

Key Concepts
  • Switch expressions return a value and use arrow syntax that eliminates fall-through without requiring break statements
  • The yield keyword returns a value from a multi-statement block case
  • The compiler enforces exhaustiveness: all possible values must be handled or a default is required
  • Multiple labels per case are supported with comma separation: case SAV, CUR -> ...
  • Pattern matching switch (Java 21) adds type matching with guards: case Integer i when i > 0 -> ...
  • Adding a new enum constant without updating the switch is a compile error, preventing a class of bugs
  • Final in Java 14 via JEP 361; pattern matching final in Java 21 via JEP 441

Definition

Switch expressions return a value using arrow syntax with no fall-through, enforce exhaustiveness at compile time, and use yield for multi-statement blocks.
DIAGRAM
- **Arrow syntax** – `case X -> result;` replaces the colon form and has no fall-through, so `break` is never needed.
- **Expression** – the entire switch can be assigned to a variable: `BigDecimal rate = switch (type) { ... };`
- **yield** – returns a value from a multi-statement block: `case FD -> { var r = compute(); yield r; }`
- **Exhaustiveness** – the compiler requires all possible values to be handled; over an enum, every constant must appear or a `default` is present.
- **Multiple labels** – `case SAV, CUR ->` handles multiple values in one arm.
- **Pattern matching** – Java 21 adds `case Integer i when i > 0 ->` for type patterns with guards.

Classic switch is like a bank queue where customers accidentally walk from counter 1 to counter 2 if no one says "stop" (fall-through). Switch expressions are like a ticketing system: you get your result at one counter and you are done. Plus the bank guarantees every customer type has a counter (exhaustiveness).

Understanding the Concepts

What are switch expressions?

Switch expressions (JEP 361, final in Java 14) let switch return a value. The arrow form case X -> result; has no fall-through, so you never need break. You can group labels with commas: case SAV, CUR -> ....

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class InterestCalculator {

    enum AccountType { SAVINGS, CURRENT, FD, RD, NRI }

    public BigDecimal getRate(AccountType type) {
        return switch (type) {
            case SAVINGS -> new BigDecimal("3.5");
            case CURRENT -> BigDecimal.ZERO;
            case FD      -> new BigDecimal("7.1");
            case RD      -> new BigDecimal("6.5");
            case NRI     -> new BigDecimal("4.0");
            // Exhaustive: all enum constants covered. No default needed.
        };
    }
}

Why is fall-through elimination important?

Classic switch with colon syntax falls through to the next case unless you write break. Forgetting break is one of the most common Java bugs.

JAVA
// WRONG: classic switch with forgotten break
String description;
switch (type) {
    case SAVINGS:
        description = "Savings account";
        // MISSING break! Falls through to CURRENT!
    case CURRENT:
        description = "Current account";
        break;
    default:
        description = "Unknown";
}
// description is "Current account" even when type is SAVINGS!

// CORRECT: switch expression, no fall-through possible
String description = switch (type) {
    case SAVINGS -> "Savings account";
    case CURRENT -> "Current account";
    default -> "Unknown";
};

How does yield work?

When a case arm needs multiple statements, use a block with yield to return the value.

JAVA
public BigDecimal calculateInterest(AccountType type, BigDecimal balance) {
    BigDecimal rate = switch (type) {
        case FD -> {
            BigDecimal baseRate = new BigDecimal("7.1");
            BigDecimal seniorBonus = isSeniorCitizen() ? new BigDecimal("0.5") : BigDecimal.ZERO;
            yield baseRate.add(seniorBonus);  // yield returns from the block
        }
        case SAVINGS -> new BigDecimal("3.5");
        case CURRENT -> BigDecimal.ZERO;
        default -> new BigDecimal("4.0");
    };
    return balance.multiply(rate).divide(new BigDecimal("100"));
}

What is exhaustiveness checking?

The compiler requires all possible values to be handled. For enums, every constant must appear. For sealed types, every permitted subtype must be covered.

JAVA
sealed interface Transaction permits Debit, Credit, Reversal {}
record Debit(BigDecimal amount) implements Transaction {}
record Credit(BigDecimal amount) implements Transaction {}
record Reversal(String txnId) implements Transaction {}

public String describe(Transaction txn) {
    return switch (txn) {
        case Debit d   -> "Debit of Rs." + d.amount();
        case Credit c  -> "Credit of Rs." + c.amount();
        case Reversal r -> "Reversal of " + r.txnId();
        // Adding a new subtype without handling it here is a COMPILE ERROR
    };
}

What is pattern matching switch?

Java 21 (JEP 441) adds type patterns and guarded patterns to switch.

JAVA
public String classify(Object input) {
    return switch (input) {
        case Integer i when i > 100_000 -> "Large amount: " + i;
        case Integer i                  -> "Small amount: " + i;
        case String s when s.startsWith("SAV") -> "Savings account: " + s;
        case String s                   -> "Text: " + s;
        case BigDecimal bd              -> "Decimal: " + bd;
        case null                       -> "Null input";
        default                         -> "Unknown: " + input;
    };
}

How is a switch expression different from a switch statement?

| Aspect | Switch Statement | Switch Expression |
|---|---|---|
| Returns value | No | Yes (assign to variable) |
| Fall-through | Yes (colon syntax) | No (arrow syntax) |
| Break needed | Yes | No |
| Exhaustiveness | Not enforced | Enforced by compiler |
| Pattern matching | Not supported | Supported (Java 21) |
| yield | Not available | Returns value from block |

What are common mistakes?

JAVA
// WRONG: mixing arrow and colon syntax
int result = switch (type) {
    case SAVINGS -> 1;
    case CURRENT: yield 2;  // COMPILE ERROR: cannot mix arrow and colon
};

// WRONG: using return instead of yield
int result = switch (type) {
    case SAVINGS -> {
        return 1;  // WRONG: return exits the METHOD, not the switch
    }
    default -> 0;
};
// CORRECT: yield exits the switch block
int result = switch (type) {
    case SAVINGS -> {
        yield 1;   // yields value to the switch expression
    }
    default -> 0;
};

What should a senior engineer know?

DIAGRAM
- Switch expressions over sealed types with record patterns give algebraic-data-type style modeling in Java.
- `null` can be handled explicitly: `case null -> ...`. Without it, a null input throws NullPointerException.
- Guard conditions use `when`: `case String s when s.length() > 10 -> ...`. Guards are evaluated after the type match.
- Dominance ordering: specific patterns must come before general ones. `case Integer i when i > 0` must precede `case Integer i`.

What are common interview follow-up questions?

DIAGRAM
- Can you use switch expressions with strings? (Yes, `case "SAV" -> ...` works.)
- What happens if you add a new enum constant? (All switch expressions over that enum fail to compile until the new constant is handled.)
- Is the arrow switch always an expression? (No, you can use arrow syntax in a switch statement too, but it won't return a value.)

Visual Overview

DIAGRAM
  Classic Switch (fall-through risk):
  ┌────────────────────────┐
  │ case SAVINGS:          │
  │   rate = 3.5;          │
  │   // no break!         │
  │ case CURRENT:  ◀─ falls through!
  │   rate = 0.0;          │
  │   break;               │
  └────────────────────────┘

  Switch Expression (no fall-through):
  ┌────────────────────────┐
  │ case SAVINGS -> 3.5    │ ──▶ done
  │ case CURRENT -> 0.0    │ ──▶ done
  │ case FD -> {           │
  │   compute();           │
  │   yield 7.1;           │ ──▶ done
  │ }                      │
  └────────────────────────┘
  Exhaustive: compiler checks all cases covered

Key Takeaway

Switch expressions return a value using arrow syntax that eliminates fall-through, the most common switch bug. The yield keyword returns a value from multi-statement blocks. The compiler enforces exhaustiveness: every enum constant or sealed subtype must be handled, so adding a new type without updating the switch is a compile error.

Pattern matching switch (Java 21) adds type patterns and guards: case Integer i when i > 0 ->. Combined with sealed interfaces and records, this gives Java algebraic-data-type modeling where the compiler verifies all cases are covered. The code is concise, safe, and reads like a decision table.

In a banking interest calculator, mapping account types to rates is a single switch expression: case SAVINGS -> new BigDecimal("3.5"); case FD -> new BigDecimal("7.1");. No fall-through risk, no missing break, and adding a new account type like NRI forces every switch to be updated.

Remember one sentence: switch expressions return values with arrow syntax, enforce exhaustiveness at compile time, and use yield for multi-statement blocks.

---

Share
Use ← → arrow keys to navigate