Part 6 of 14

Switch Expressions (JEP 361): Switch as an Expression, Not Just a Statement

Finalized in Java 14 (JEP 361). Available in all Java 14+ releases, including Java 17. Previous previews: Java 12 (JEP 325) and Java 13 (JEP 354).

The Problem with Switch Statements

Java’s traditional switch statement has two well-known pain points:

Pain 1: Fall-Through

// Java 11 — easy to introduce fall-through bugs
switch (day) {
    case MONDAY:
        result = "Start of work week";
        break; // easy to forget this
    case TUESDAY:
        result = "Weekday";
        // no break — falls through to WEDNESDAY case
    case WEDNESDAY:
        result = "Hump day";
        break;
    default:
        result = "Other";
}

A missing break causes the next case to execute unexpectedly. This is a classic Java bug — the compiler has no way to warn you that fall-through was unintentional.

Pain 2: Switch Is a Statement, Not an Expression

Switch in Java 11 is a statement — it cannot return a value directly. To use switch to assign a variable, you must pre-declare it:

// Java 11 — pre-declare, then assign inside switch
String result;
switch (day) {
    case MONDAY:
    case FRIDAY:
        result = "Bookend";
        break;
    case WEDNESDAY:
        result = "Middle";
        break;
    default:
        result = "Other";
}
// result might be uninitialized if no case matched and no default

This pattern is verbose and the compiler cannot easily verify that result is definitely assigned.


Switch Expressions: The Solution

Java 14 introduces switch expressions — switch as a first-class expression that returns a value.

Arrow Switch Expression

// Java 17 — switch expression with arrow syntax
String result = switch (day) {
    case MONDAY, FRIDAY -> "Bookend";
    case TUESDAY, THURSDAY -> "Midweek";
    case WEDNESDAY -> "Hump day";
    case SATURDAY, SUNDAY -> "Weekend";
};

Key differences from the statement form:

  1. No break needed — each case ends at ->. No fall-through.
  2. Multiple labelscase MONDAY, FRIDAY -> handles both in one case.
  3. Expression, not statement — the result is assigned directly to result.
  4. Exhaustiveness — the compiler verifies all enum values are covered (or there is a default).

Traditional Switch Statements Still Work

Switch expressions do not replace switch statements. The traditional form is still valid. Arrow syntax can also be used in switch statements (without producing a value):

// Arrow syntax in a statement context (no value produced)
switch (level) {
    case INFO  -> logger.info(message);
    case WARN  -> logger.warn(message);
    case ERROR -> logger.error(message);
}

Arrow Cases vs Colon Cases

Switch expressions support both syntaxes:

Arrow Syntax (->)

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
};
  • No fall-through
  • Single expression or block per case
  • Most idiomatic for switch expressions

Colon Syntax with yield

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

Colon syntax with yield exists to support legacy-style switch in expression context. Arrow syntax is preferred in new code.


The yield Statement

yield produces a value from a multi-statement case block in a switch expression:

String description = switch (code) {
    case 200 -> "OK";
    case 404 -> "Not Found";
    case 500 -> {
        String msg = "Server Error";
        log.error("HTTP 500 encountered");
        yield msg;  // return value from the block
    }
    default -> "Unknown";
};

yield is only valid inside a switch expression. In a switch statement, you still use break. This distinction is intentional — yield makes the control flow clear.

return inside a switch expression returns from the enclosing method (not from the case). This is different from yield, which produces the switch expression’s value.


Exhaustiveness

Switch expressions on sealed types and enums are checked for exhaustiveness by the compiler:

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

// Missing SATURDAY and SUNDAY — compile error
String type = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
    // error: switch expression does not cover all possible input values
};

// Correct: all cases or default
String type = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
    case SATURDAY, SUNDAY -> "Weekend";
};

Exhaustiveness at compile time means no default is needed when all enum values are covered — and you get a compile error if you add a new enum value without updating all switch expressions.

This is one of the main motivations for sealed classes (Article 7): they enable the compiler to verify exhaustiveness for arbitrary type hierarchies, not just enums.


Switch Expressions with Sealed Classes (Preview in Java 17)

In Java 17, switching on sealed types with pattern matching is a preview feature (Article 8). But switching on sealed types by instance is not yet supported without --enable-preview. The combination becomes final in Java 21.

In Java 17 without preview, you can switch on the value of sealed class instances using standard equality-based cases:

sealed interface Status permits Active, Inactive, Suspended {}
record Active() implements Status {}
record Inactive() implements Status {}
record Suspended(String reason) implements Status {}

// This requires --enable-preview in Java 17 (Article 8)
// In Java 17 without preview, use instanceof chain

Switch on Strings, Integers, and Enums

Switch expressions work on all types supported by traditional switch: int, Integer, long, Long, String, and enum types.

String Switch

String response = switch (httpMethod.toUpperCase()) {
    case "GET"    -> fetchResource(id);
    case "POST"   -> createResource(body);
    case "PUT"    -> updateResource(id, body);
    case "DELETE" -> deleteResource(id);
    default       -> throw new UnsupportedOperationException(httpMethod);
};

Integer Switch

String grade = switch (score / 10) {
    case 10, 9 -> "A";
    case 8     -> "B";
    case 7     -> "C";
    case 6     -> "D";
    default    -> "F";
};

Throwing Exceptions in Switch Expressions

A case can throw an exception — this satisfies the requirement that every path produces a value (or throws):

String season = 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);
};

Returning Switch Expressions from Methods

Switch expressions can be used directly in return statements:

static String format(Object value) {
    return switch (value) {
        case Integer i -> "Int: " + i;
        case Double d  -> "Double: " + String.format("%.2f", d);
        case String s  -> "String: \"" + s + "\"";
        default        -> "Unknown: " + value;
    };  // Note: this is a preview feature in Java 17 — see Article 8
}

Non-pattern switch expressions in return:

static String dayType(DayOfWeek day) {
    return switch (day) {
        case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
        case SATURDAY, SUNDAY -> "Weekend";
    };
}

Common Patterns

Configuration Selection

DataSource dataSource = switch (environment) {
    case "dev"  -> new H2DataSource();
    case "test" -> new TestContainerDataSource();
    case "prod" -> new PostgresDataSource(config);
    default     -> throw new IllegalStateException("Unknown env: " + environment);
};

Enum to Value Mapping

enum Priority { LOW, MEDIUM, HIGH, CRITICAL }

int slaHours = switch (priority) {
    case LOW      -> 72;
    case MEDIUM   -> 24;
    case HIGH     -> 4;
    case CRITICAL -> 1;
};

Multi-Statement Case with Logging

String status = switch (code) {
    case 0 -> "Success";
    case 1 -> "Partial failure";
    case -1 -> {
        metrics.increment("errors");
        log.error("Critical error code -1");
        yield "Critical failure";
    }
    default -> {
        log.warn("Unknown code: " + code);
        yield "Unknown";
    }
};

Switch Expressions vs Ternary and if-else

ConstructUse case
Ternary ? :Two alternatives, simple expressions
if-else if-elseComplex conditions, side effects
Switch expressionMultiple alternatives based on a single value
Switch statementMultiple alternatives with side effects

Switch expressions are most valuable when you have 3 or more cases based on a discrete value.


What Cannot Be in a Switch Expression

  • No return (to return from enclosing method — use at the method level, not inside a case)
  • No break (breaks are for switch statements)
  • No continue (for loop continuation)
  • An arrow case must have a single expression, a block, or a throw — not multiple statements without a block

Summary

FeatureDetail
Arrow syntaxcase X -> expr; — no fall-through
Multiple labelscase A, B, C ->
Multi-statement casecase X -> { ... yield value; }
yieldReturns value from a block inside switch expression
ExhaustivenessCompiler-enforced for enums; requires default for other types
Null handlingnull is not matched by any case; throws NullPointerException at the switch
Colon syntaxcase X: yield value; — supports fall-through, less idiomatic

What’s Next

Article 7: Sealed Classes (JEP 409) covers how to define closed type hierarchies where the compiler knows all possible subtypes — enabling exhaustive switch without default.