Comprehensive Guide to Switch Expressions in Java

Target versions: Java 12+, finalized in Java 14
Core JEPs:
- JEP 325 – Switch Expressions (Preview, Java 12)
- JEP 354 – Switch Expressions (2nd Preview, Java 13)
- JEP 361 – Switch Expressions (Standard, Java 14)


1. Overview

Traditionally, switch in Java was a statement only:

  • It did not yield a value.
  • It required explicit break statements to avoid fall-through.
  • Syntax was verbose and error-prone.

Switch expressions modernize switch by:

  1. Allowing switch to be used as an expression (that returns a value).
  2. Introducing a new arrow (->) syntax.
  3. Providing exhaustiveness checks in some cases (especially with enums and sealed types in later Java versions).
  4. Eliminating most fall-through bugs by default.

From Java 14 onward, switch expressions are a final, standard feature of the language.


2. Old vs New: Basic Comparison

2.1. Traditional switch Statement (Before Java 12)

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 IllegalStateException("Unknown day: " + day);
}

Issues:

  • Repetition of numLetters =.
  • Easy to forget break and cause fall-through bugs.
  • Verbose.

2.2. Switch Expression with Arrow Syntax (Java 14+)

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY   -> 6;
    case TUESDAY                 -> 7;
    case THURSDAY, SATURDAY      -> 8;
    case WEDNESDAY               -> 9;
};

Key differences:

  • switch now yields a value.
  • case labels can be comma-separated (multi-label cases).
  • No break required — no fall-through with ->.
  • Expression must be exhaustive (must cover all possible values or have default).

3. Syntax Forms

Switch expressions support two main syntactic styles:

  1. Arrow form (->)
  2. Block form with yield (for multi-statement cases)

3.1. Arrow Form (Expression Case)

var result = switch (value) {
    case 1      -> "one";
    case 2, 3   -> "two or three";
    default     -> "other";
};

Each case directly produces a value on the right-hand side of ->.

  • The switch expression’s value is what the selected case “returns”.
  • Arrow form cases cannot fall through; each case is independent.

3.2. Block Form with yield (Statements in a Case)

If you need multiple statements in a case (e.g., logging, intermediate variables), you use a block and yield:

String description = switch (code) {
    case 1 -> "OK";
    case 2 -> "Warning";
    case 3 -> {
        log("Error encountered with code 3");
        yield "Error";  // 'yield' returns the value of this case
    }
    default -> {
        String msg = "Unknown code: " + code;
        yield msg;
    }
};

Notes:

  • Inside a block case { ... }, use yield <expression>; to provide the result for that case.
  • yield is a contextual keyword; it acts like return but for a switch expression case, not for the whole method.

3.3. Switch as a Statement (Still Allowed)

You can still use switch as a statement like before. Both forms now coexist:

switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println("6 letters");
    case TUESDAY                -> System.out.println("7 letters");
    default                     -> System.out.println("Other");
}
  • Here, the switch is a statement (no value captured).
  • Arrow syntax can be used with side effects only.

4. Types and Allowed Switch Targets (Updated Rules)

Switch expressions extend the traditional rules.

4.1. Allowed Types in Modern Java

Historically, switch supported:

  • byte, short, char, int
  • Their wrappers: Byte, Short, Character, Integer
  • String
  • enum types

With more recent Java versions (especially with pattern matching), the capabilities extend to patterns, but for plain switch expressions (without patterns), the above remain the core.


4.2. Type of the Switch Expression

The type of a switch expression is determined by:

  • The types of all result expressions (the right-hand sides of -> or yield).
  • Java’s usual rules for finding a common type (like in conditional expressions / ternary operator).

Example:

var x = switch (flag) {
    case true  -> 1;
    case false -> 2;
}; // type: int

Mixed types:

var y = switch (value) {
    case 0  -> 1;        // int
    case 1  -> 1L;       // long
    default -> 1;        // int
}; // type: long (due to numeric promotion)

All case result expressions must be compatible with a single target type.


5. Exhaustiveness and default

For switch expressions, the compiler enforces that all possible values are covered:

  • If you don’t cover all cases, you must provide a default.
  • With enum types (and later sealed types), the compiler can check exhaustiveness more strictly.

5.1. With Enums

enum TrafficLight { RED, YELLOW, GREEN }

String action = switch (signal) {
    case RED    -> "STOP";
    case YELLOW -> "WAIT";
    case GREEN  -> "GO";
    // No default needed, because all enum constants are covered
};

If you forget one:

// ❌ Compile-time error: not exhaustive
String action = switch (signal) {
    case RED    -> "STOP";
    case GREEN  -> "GO";
};

You must either:

  • Add the missing YELLOW, or
  • Add default -> ....

5.2. With Primitive Types and Strings

For non-enum, non-sealed types (e.g., int, String), you typically must have a default case unless all possible values are clearly covered (which is rare outside enums).

String message = switch (status) {
    case "OK"    -> "All good";
    case "WARN"  -> "Check this";
    case "ERROR" -> "Bad";
    default      -> "Unknown status";
};

6. Fall-through and break Behavior

One of the biggest differences between classic switch and switch expressions is fall-through behavior.

6.1. With Arrow Syntax (->)

  • There is no fall-through between cases.
  • Each case is independent; no need for break.
switch (day) {
    case MONDAY  -> System.out.println("Mon");
    case TUESDAY -> System.out.println("Tue");
    default      -> System.out.println("Other");
}

Here, only one branch executes.


6.2. Traditional Colon Syntax (:) Still Supports Fall-through

Even in newer Java versions, the colon syntax still behaves like before:

switch (value) {
    case 1:
        System.out.println("one");
        // fall-through
    case 2:
        System.out.println("two");
        break;
    default:
        System.out.println("other");
}

For switch expressions, colon-style cases require careful use of break or yield.


6.3. Colon Syntax with Switch Expressions (Advanced / Rare)

You can use colon syntax in a switch expression and then use yield inside:

int result = switch (n) {
    case 1:
        System.out.println("one");
        yield 10;
    case 2:
        System.out.println("two");
        yield 20;
    default:
        yield 0;
};

Important:

  • You must use yield to provide the value for the entire switch expression.
  • break in a switch expression is not used to provide a value.

This pattern is less common in modern style; arrow syntax is preferred.


7. yield Keyword in Detail

yield is introduced specifically to return a value from a switch expression case block.

String result = switch (n) {
    case 1 -> "one";
    case 2 -> {
        log("Processing 2");
        yield "two";   // <== this is the value of the case
    }
    default -> "other";
};

Notes:

  • yield is a contextual keyword:
  • It acts like a keyword inside switch, but can still be used as an identifier in other contexts in older code.
  • Using return inside a switch expression block returns from the method, not from the switch case.

Bad:

String result = switch (n) {
    case 2 -> {
        return "two";   // ❌ returns from method, not just switch
    }
    default -> "other";
};

Always use yield to return from a case in a switch expression block.


8. Examples of Common Usage Patterns

8.1. Mapping Enums to Values

enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

int workingHours = switch (day) {
    case SATURDAY, SUNDAY -> 0;
    default               -> 8;
};

8.2. Simple String Routing

String httpMethod = "POST";

boolean isWrite = switch (httpMethod) {
    case "POST", "PUT", "PATCH", "DELETE" -> true;
    case "GET", "HEAD", "OPTIONS"         -> false;
    default                               -> throw new IllegalArgumentException("Unknown method: " + httpMethod);
};

8.3. Throwing Exceptions in a Case

int code = switch (status) {
    case "OK"    -> 200;
    case "WARN"  -> 300;
    case "ERROR" -> 500;
    default      -> throw new IllegalArgumentException("Unknown status: " + status);
};

8.4. Complex Cases with Additional Logic

String normalized = switch (input) {
    case null -> "null";
    case ""   -> "empty";
    default   -> {
        String trimmed = input.trim();
        String lower   = trimmed.toLowerCase();
        yield lower;
    }
};

9. Limitations & Edge Cases

9.1. Exhaustiveness Requirements

A switch expression must always have a value.

  • Every execution path must yield a value.
  • If a case block doesn’t yield, and control could reach the end of the block, it’s a compile-time error.
// ❌ illegal
int result = switch (n) {
    case 1 -> {
        if (someCondition)
            yield 10;
        // missing yield if someCondition is false
    }
    default -> 0;
};

Fix:

int result = switch (n) {
    case 1 -> {
        if (someCondition)
            yield 10;
        yield 20; // ensure all paths yield
    }
    default -> 0;
};

9.2. Types Must Be Compatible

All branch result types must have a common type.

var x = switch (n) {
    case 1  -> "one";    // String
    default -> 2;        // int
};
// ❌ compile error: incompatible types

Fix by using consistent types:

var x = switch (n) {
    case 1  -> "one";
    default -> "two";
};

Or by explicitly choosing a wider type:

Object x = switch (n) {
    case 1  -> "one";
    default -> 2;
};

9.3. Side Effects vs Expressions

switch expressions are not required to be side-effect free, but you should be mindful:

int result = switch (n) {
    case 1 -> {
        log("n=1");
        yield 1;
    }
    default -> 0;
};

Design tip: treat them similarly to expression-oriented programming (like in Kotlin / Scala) when possible, keeping side effects minimal.


9.4. Interaction with Pattern Matching for switch (Later Java Versions)

In Java 17+ (preview) and Java 21+ (final), pattern matching for switch extends switch expressions with patterns, instanceof-like matching, and more advanced exhaustiveness checks for sealed hierarchies.

Basic example (conceptual, pattern matching enabled):

String result = switch (obj) {
    case String s       -> "String of length " + s.length();
    case Integer i      -> "Integer " + i;
    case null           -> "null";
    default             -> "Other type";
};

Even without deeply using patterns, understanding switch expressions is foundational for pattern matching.


10. Style Guidelines and Best Practices

10.1. Prefer Arrow Syntax for New Code

Use the arrow form for most modern code:

var result = switch (x) {
    case 1      -> "one";
    case 2, 3   -> "two or three";
    default     -> "other";
};

It’s:

  • More concise
  • No fall-through
  • Easier to read

10.2. Use Expression Form for Value Computation

When you need a value, use switch as an expression instead of:

String label;
switch (code) {
    case 1 -> label = "one";
    case 2 -> label = "two";
    default -> label = "other";
}

Prefer:

String label = switch (code) {
    case 1      -> "one";
    case 2      -> "two";
    default     -> "other";
};

10.3. Use default Carefully

  • For enum and sealed types, aiming to avoid default can sometimes be beneficial, because adding a new constant/type will cause a compile-time error instead of silently falling into default.
  • That’s more relevant once you combine switch expressions with these advanced types.

10.4. Don’t Mix Side Effects and Values Unnecessarily

While legal, combining heavy side effects with value-computing switch expressions can reduce readability:

String label = switch (code) {
    case 1 -> {
        audit(code);
        log("one");
        yield "one";
    }
    default -> {
        audit(code);
        yield "other";
    }
};

Consider extracting side effects or at least keeping logic simple and readable.


11. Typical Interview Questions (with Short Answers)

Q1. What is the difference between a switch statement and a switch expression?

  • Statement: does not yield a value; used for side effects.
  • Expression: yields a value; can be assigned to a variable or returned.
  • Switch expressions introduced arrow syntax, yield, and better exhaustiveness.

Q2. In which Java version were switch expressions finalized?

  • Final in Java 14 (after previews in 12 and 13).

Q3. What is the purpose of the yield keyword?

  • It returns a value from a case block in a switch expression.
  • It is similar to return but for the switch expression, not the enclosing method.

Q4. Can switch expressions fall through from one case to another?

  • With arrow (->) syntax, no fall-through.
  • With colon (:) syntax, traditional fall-through rules still apply, but you must ensure a value is yielded for the expression.

Q5. What types can be used in a modern switch?

  • Primitive integral types (byte, short, char, int) and their wrappers
  • String
  • enum types
  • With pattern matching (later): various reference types and patterns

Q6. What happens if a switch expression is not exhaustive?

  • The code will not compile unless you provide a default or cover all cases (e.g., all enum constants).

12. Summary

  • Switch expressions bring Java closer to modern expression-oriented language design.
  • They reduce boilerplate, eliminate many fall-through bugs, and integrate nicely with later features like pattern matching and sealed types.
  • Key concepts: arrow syntax (->), yield in block cases, exhaustiveness, and value type inference.
  • Understanding switch expressions is essential for idiomatic Java from Java 14 onward.

This document should give you enough depth on switch expressions (syntax, semantics, examples, limitations, and interview angles) so that you don’t need to look elsewhere when preparing for interviews or writing modern Java code.