Switch Expressions in Java

Master Java switch expressions: using switch as an expression that returns values, pattern matching in Java 14+, and modernizing switch statement syntax.

published: reading time: 12 min read author: Geek Workbench

Switch Expressions in Java

Switch expressions, introduced in Java 14 (standardized in Java 17), modernize the traditional switch statement by enabling it to function as an expression that returns values. This eliminates many boilerplate patterns and enables more expressive code.

Introduction

Switch expressions, introduced as a preview feature in Java 12 and standardized in Java 17, modernize the traditional switch statement by treating switch as an expression that returns a value rather than a statement that executes code. The new arrow syntax (case ->) eliminates the infamous fall-through bug that plagues the traditional colon syntax, and exhaustive checking at compile time prevents runtime errors from unhandled cases.

The key improvement is that switch expressions integrate into the expression-oriented style of modern Java. Instead of assigning to a variable inside a switch statement and returning it separately, you can return the switch expression’s result directly. Combined with pattern matching (available in Java 21+), switch becomes a powerful tool for type-based branching that eliminates chains of instanceof checks and manual casts.

This post covers the modern arrow syntax and yield keyword, how switch expressions differ from traditional switch statements, pattern matching in switch with type guards, the exhaustiveness requirement and when it applies, and migration strategies for existing switch code.

When to Use / Not to Use

Use switch expressions when:

  • Selecting from multiple discrete values based on a single expression
  • Mapping input to output values (functional style)
  • Replacing simple if-else-if chains with mutually exclusive cases
  • Working with enum-based branching

Do not use switch expressions when:

  • Conditions span ranges—use if-else with comparisons
  • Complex conditions involving multiple variables—use if-else
  • Only two branches—use simple if-else or ternary operator
  • Fall-through behavior is needed (use traditional switch with explicit break)

Diagram: Switch Expression Flow

flowchart TD
    A["switch (expression)"] --> B{"Match Case?"}
    B -->|case A| C["yield valueA"]
    B -->|case B| D["yield valueB"]
    B -->|case C| E["yield valueC"]
    B -->|default| F["yield defaultValue"]
    C --> G["Result = valueA"]
    D --> G
    E --> G
    F --> G
    G --> H["Continue"]

Code Snippet: Traditional vs Modern Switch

public class SwitchExpressionDemo {

    // Traditional switch statement (Java 12-13 preview)
    public static String getDayTypeOld(int day) {
        String result;
        switch (day) {
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
                result = "Weekday";
                break;
            case 6:
            case 7:
                result = "Weekend";
                break;
            default:
                result = "Invalid";
        }
        return result;
    }

    // Modern switch expression (Java 14+, standard Java 17)
    public static String getDayTypeNew(int day) {
        return switch (day) {
            case 1, 2, 3, 4, 5 -> "Weekday";
            case 6, 7 -> "Weekend";
            default -> "Invalid";
        };
    }

    // Switch expression with blocks (using yield)
    public static String getDayDescription(int day) {
        return switch (day) {
            case 1, 2, 3, 4, 5 -> {
                String desc = "Work day";
                yield desc + " (Monday-Friday)";
            }
            case 6 -> "Saturday - rest day";
            case 7 -> "Sunday - rest day";
            default -> {
                if (day < 1) {
                    yield "Negative day - impossible";
                } else {
                    yield "Day > 7 - impossible";
                }
            }
        };
    }

    // Pattern matching (Java 17+ preview, Java 21 stable)
    public static String describe(Object obj) {
        return switch (obj) {
            case Integer i when i > 0 -> "Positive integer: " + i;
            case Integer i -> "Non-positive integer: " + i;
            case String s -> "String of length " + s.length();
            case null -> "Null value";
            default -> "Something else: " + obj.getClass().getSimpleName();
        };
    }

    public static void main(String[] args) {
        // Basic usage
        System.out.println(getDayTypeNew(3)); // Weekday
        System.out.println(getDayTypeNew(7)); // Weekend

        // Pattern matching examples
        System.out.println(describe(42));        // Positive integer: 42
        System.out.println(describe(-5));         // Non-positive integer: -5
        System.out.println(describe("hello"));   // String of length 5
        System.out.println(describe(null));      // Null value
        System.out.println(describe(3.14));      // Something else: Double
    }
}

Failure Scenarios

ScenarioProblemSolution
Missing yield in blockCode won’t compileUse yield to return value from block
No default caseUnhandled values cause compilation error (exhaustive)Add default case or throw exception
Switch without exhaustive checkRuntime errors for unhandled casesUse sealed classes or ensure all cases covered
Using break in expressionbreak not allowed in expressionsUse yield instead

Trade-off Table

FeatureTraditional SwitchSwitch Expression
Returns valueNo (statement)Yes (expression)
Arrow syntaxNoYes (->)
Block returnsN/AVia yield
Pattern matchingNoYes (Java 17+)
Exhaustiveness checkNoYes
Fall-throughSupportedNot supported

Observability Checklist

  • Log switch input values and matched cases for debugging
  • Add metrics for case frequency distribution
  • Instrument default case hits (may indicate unexpected input)
  • Add integration tests for all case branches
  • Monitor for unhandled case patterns in production

Security Notes

  • Input validation: Switch should handle all valid inputs via exhaustive cases
  • Enum safety: Prefer switch over enums to catch missing cases at compile time when new enum values are added
  • Null handling: Explicitly handle null case or let it throw NullPointerException (documented behavior)

Pitfalls

  1. Forgetting yield: Block cases require yield, not return
  2. Missing default in production: May cause runtime errors with new values
  3. Mixing : and -> styles incorrectly: Cannot mix in same switch
  4. Exhaustive check for sealed classes: Must cover all cases or compile fails
  5. Null cases: Must be explicitly handled or will throw NullPointerException

Quick Recap

  • Switch expressions use -> syntax and return values directly
  • Use yield to return values from block cases
  • Multiple values can be comma-separated in one case: case 1, 2, 3 ->
  • Pattern matching (Java 21+) enables type-based matching
  • Switch expressions must be exhaustive for sealed types

Interview Questions

1. What is the difference between switch statements and switch expressions?

Model Answer: "A switch statement is a control flow statement that executes code blocks. A switch expression is an expression that yields a value—it can be assigned to a variable or returned directly. Switch expressions use `->` syntax and must be exhaustive for sealed types."

2. What is `yield` in a switch expression?

Model Answer: "`yield` is used in block cases (where the case body has multiple statements) to return a value from the switch expression. It replaces the need for a local variable and separate return statement. Example: `case 1 -> { int result = doSomething(); yield result; }`"

3. Can switch expressions be used as statements only?

Model Answer: "Switch expressions can be used both as expressions (returning a value) and as statements (discarding the value). When used as an expression, it must be exhaustive (all cases handled or have a default). When used as a statement, it's more flexible."

4. What is pattern matching in switch (Java 21+)?

Model Answer: "Pattern matching allows matching on the type of an object directly in the case label. For example: `case String s -> "String: " + s`. With guards: `case Integer i when i > 0 -> "Positive"`. This eliminates the need for instanceof checks and casts."

5. Why must switch expressions be exhaustive?

Model Answer: "Exhaustive switch expressions (when used as expressions) ensure all possible input values are handled. If a case is missing, compilation fails. This prevents runtime errors from unhandled values and is especially useful with sealed classes and enums where the compiler knows all possible values."

6. What is the difference between `:` and `->` syntax in switch?

Model Answer: "`:` syntax (traditional) requires `break` to prevent fall-through. `->` syntax (modern) does not allow fall-through—the case body is a single expression or block, and execution stops after the case. Mixing both styles in the same switch is not allowed."

7. How does switch handle null in Java?

Model Answer: "In modern switch expressions (Java 14+), you can explicitly handle null with `case null ->`. If null is passed and no null case exists, a `NullPointerException` is thrown with a message indicating the switch caused it. Always handle null explicitly for defensive programming."

8. Can you use multiple values in a single case label?

Model Answer: "Yes, with arrow syntax: `case 1, 2, 3 -> "Small"`. Multiple comma-separated values share the same body. With colon syntax, you stack case labels: `case 1: case 2: case 3:` share the same body."

9. What are the advantages of switch expressions over if-else chains?

Model Answer: "Switch expressions are more concise, easier to read for discrete values, provide compile-time exhaustiveness checking, eliminate fall-through bugs (with `->`), and enable pattern matching. If-else chains remain necessary for range conditions or complex boolean expressions."

10. What is a sealed class and how does it relate to switch exhaustiveness?

Model Answer: "A sealed class restricts which classes can extend it. When switch is used on a sealed class (as an expression), the compiler verifies all permitted subclasses are handled—no default needed if all cases are covered. This is a form of exhaustive pattern matching at compile time."

11. What happens if a switch expression does not have a default case?

Model Answer: "If a switch expression is used as an expression and not all cases are covered, compilation fails. The compiler ensures exhaustiveness. When used as a statement (void context), a default case is optional but recommended to handle unexpected values."

12. What is the difference between `yield` and `return` in a switch block case?

Model Answer: "`yield` returns a value from the switch expression and terminates that case only. `return` in a switch block would exit the entire enclosing method, which is usually not intended. Always use `yield` for block cases in switch expressions."

13. How do switch expressions handle null values?

Model Answer: "In modern switch expressions (Java 14+), you can explicitly handle null with `case null ->`. If null is passed and no null case exists, a `NullPointerException` is thrown with a message indicating the switch caused it. Always handle null explicitly for defensive programming."

8. Can you use multiple values in a single case label?

Model Answer: "Yes, with arrow syntax: `case 1, 2, 3 -> "Small"`. Multiple comma-separated values share the same body. With colon syntax, you stack case labels: `case 1: case 2: case 3:` share the same body."

9. What are the advantages of switch expressions over if-else chains?

Model Answer: "Switch expressions are more concise, easier to read for discrete values, provide compile-time exhaustiveness checking, eliminate fall-through bugs (with `->`), and enable pattern matching. If-else chains remain necessary for range conditions or complex boolean expressions."

10. What is a sealed class and how does it relate to switch exhaustiveness?

Model Answer: "A sealed class restricts which classes can extend it. When switch is used on a sealed class (as an expression), the compiler verifies all permitted subclasses are handled—no default needed if all cases are covered. This is a form of exhaustive pattern matching at compile time."

11. What happens if a switch expression does not have a default case?

Model Answer: "If a switch expression is used as an expression and not all cases are covered, compilation fails. The compiler ensures exhaustiveness. When used as a statement (void context), a default case is optional but recommended to handle unexpected values."

14. What is the effect of using `break` in a switch expression?

Model Answer: "`break` is not allowed in switch expressions. In traditional switch statements, `break` prevents fall-through. In switch expressions with arrow syntax, there is no fall-through by default — each case is independent. Using `break` in an expression context causes a compile error."

15. How does pattern matching in switch improve code compared to instanceof checks?

Model Answer: "Pattern matching eliminates the need for separate `instanceof` checks and casts. `case String s ->` combines the type check, cast, and variable binding into one expression. With guards (`when`), you can add conditions: `case Integer i when i > 0 ->`. This reduces boilerplate and prevents casting errors."

16. What is the difference between switch expressions and pattern matching for type checking?

Model Answer: "Switch expressions select among discrete values. Pattern matching in switch (Java 21+) matches on types and shapes of objects. `case Integer i ->` checks if the value is an Integer and binds it to `i`. Combined with guards: `case String s when s.length() > 5 ->`. This eliminates chains of `instanceof` checks and manual casts."

Further Reading

Conclusion

Switch expressions modernize Java’s branching by treating switch as an expression that returns values rather than just a statement that executes code. The arrow syntax -> and yield keyword eliminate the traditional fall-through bug, and exhaustive checking at compile time prevents runtime surprises.

Pattern matching in switch (Java 21+) takes this further by matching on types directly, eliminating chains of instanceof checks. For most multi-way branching scenarios, switch expressions are cleaner than if-else chains and more maintainable than scattered conditionals.

The ternary operator remains useful for simple binary choices, while if-else statements handle non-discrete conditions better. Together, these three branching constructs cover every decision scenario you’ll encounter—choose based on whether you’re selecting values (switch, ternary) or executing statements (if-else).

Category

Related Posts

Abstract Classes in Java

Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.

#java-abstract-classes #java #java-fundamentals

Arithmetic Operators in Java

Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.

#java-arithmetic-operators #java #java-fundamentals

Array Basics in Java

Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.

#java-array-basics #java #java-fundamentals