Back to Blog
Java

Java Switch Expressions: Syntax and Usage

java switch expressions: Learn how Java switch expressions work, including arrow syntax, yield, exhaustiveness, and practical differences from switch statements.

JavaSwitch ExpressionsArrow SyntaxYieldExhaustive Switch
Illustration of Java switch expression with arrow syntax and yield keyword

Java switch expressions, introduced as a standard feature in Java 14, let you use a switch construct as an expression that produces a value. Unlike the traditional switch statement, a switch expression does not fall through, and it must be exhaustive. This article explains the syntax, the role of yield, and the practical differences that matter when you refactor existing code.

The Core Syntax: Arrow Labels and Expressions

The most visible change in a switch expression is the arrow syntax. Instead of writing case X: and relying on break to prevent fallthrough, you write case X -> followed by an expression, a block, or a throw statement. The arrow form is also allowed in a switch statement, but it is the default style for expressions.

int dayNumber = switch (day) { case MONDAY -> 1; case TUESDAY -> 2; case WEDNESDAY -> 3; case THURSDAY -> 4; case FRIDAY -> 5; case SATURDAY -> 6; case SUNDAY -> 7; };

Each arrow branch is independent. There is no fallthrough, so you do not need break or continue. The right-hand side of an arrow can be a single expression, a block, or a throw statement. When it is a block, you must use yield to return a value from the switch expression.

Using yield in a Block Body

A block body is useful when a case needs multiple statements before producing a result. Inside the block, yield returns a value from the surrounding switch expression. yield is a keyword only within a switch expression; outside that context, it is not a reserved word.

String message = switch (errorCode) { case 404 -> { String detail = "Not Found"; yield "Resource missing: " + detail; } case 500 -> { String detail = "Internal Server Error"; yield "Server failure: " + detail; } default -> "Unknown error"; };

Here, each block computes a string and uses yield to hand it back to the expression. The default branch is required when the switch is not exhaustive over the possible values. For an int or a String, the compiler cannot know all possible values, so default is mandatory.

Exhaustiveness and the default Branch

A switch expression must be exhaustive: for any possible value of the selector, there must be a matching case. For enum types, the compiler can check that all constants are covered. If you omit default and cover every enum constant, the switch is exhaustive. For numeric, string, or other reference types, you must include default.

enum Direction { NORTH, SOUTH, EAST, WEST } String abbreviation = switch (direction) { case NORTH -> "N"; case SOUTH -> "S"; case EAST -> "E"; case WEST -> "W"; };

If you later add a new enum constant, the switch expression will no longer compile until you handle it. This is a compile-time safety net that switch statements do not provide.

Switch Expressions vs. Switch Statements

A switch statement is a control-flow construct that can execute multiple statements and does not produce a value. A switch expression is an expression that always yields a value. The table below summarizes the key differences.

AspectSwitch StatementSwitch Expression
Produces a valueNoYes
FallthroughPossible unless break is usedNot allowed
Arrow syntaxAllowed (Java 14+)Standard
Exhaustiveness checkNot enforcedEnforced by compiler
yield keywordNot usedUsed in block bodies

When you only need to perform side effects and do not need a result, a switch statement is still appropriate. However, if you are assigning a variable based on a selector, a switch expression often leads to more concise and less error-prone code.

Pattern Matching and Switch Expressions

Pattern matching for switch is a separate feature that was finalized in Java 21. It allows you to match on type patterns and use guarded patterns. While it builds on the same arrow syntax, it is not part of the original switch expressions feature. If you are using a Java version before 21, you cannot use type patterns in a switch expression. The core switch expression syntax remains unchanged, but the combination of pattern matching and exhaustiveness makes the default case unnecessary when you cover all possible types.

Object obj = ...; String result = switch (obj) { case Integer i -> "Integer: " + i; case String s -> "String: " + s; case null -> "null"; default -> "Unknown type"; };

This example is valid in Java 21 and later. Notice the null case, which was previously not allowed in switch expressions. If you are targeting an older Java version, you must handle null before the switch or use a default that covers it.

Performance and Maintainability Considerations

Switch expressions do not introduce a performance penalty compared to switch statements. The bytecode generated for an arrow-based switch is essentially the same as a traditional switch with break statements. The real benefit is maintainability: the compiler enforces exhaustiveness, and the absence of fallthrough eliminates a common source of bugs.

From a runtime perspective, the JVM can optimize switch expressions using the same mechanisms as switch statements, such as tableswitch or lookupswitch. You should not micro-optimize by choosing one form over the other; instead, choose the form that makes the code clearer. If a switch expression becomes too large, consider extracting each branch into a separate method or using polymorphism to avoid a long chain of cases.

Common Pitfalls and How to Avoid Them

One common mistake is forgetting that a switch expression must produce a value on every path. If you use a block body and forget yield, the code will not compile. Another mistake is using the colon form (case X:) inside a switch expression. The colon form is only valid in a switch statement; in an expression, you must use arrows. Mixing the two forms in the same switch is not allowed.

A subtle issue arises when you use return inside a switch expression. return exits the enclosing method, not the switch expression. If you need to return a value from the switch expression itself, use yield. For example:

// Incorrect: return exits the method, not the switch int result = switch (value) { case 1 -> return 10; // compile error default -> 0; };
// Correct: yield returns from the switch expression int result = switch (value) { case 1 -> yield 10; default -> 0; };

Another edge case is handling null. A switch expression throws NullPointerException if the selector is null and no null case is present. In Java 21 and later, you can add a case null to handle it explicitly. In earlier versions, you must check for null before the switch.

Choosing Between Switch Expressions and Other Constructs

Switch expressions are not always the best choice. If you have only two or three cases, an if-else chain may be more readable. If the logic varies significantly per case and involves complex behavior, polymorphism or a Map of functions may be a better design. Switch expressions shine when you have a fixed set of values, the logic per case is simple, and you need to assign a result. They also work well in functional-style code where you want to avoid mutable variables and side effects.

When you are refactoring an existing switch statement that assigns a variable, consider converting it to a switch expression. The compiler will guide you to add missing cases, and the result is often more compact. However, if the switch statement contains many statements per case, a switch expression with block bodies and yield may not reduce verbosity much. In that situation, weigh the exhaustiveness benefit against the added nesting.

java switch expressions: Practical Usage and Code Examples | RYUSLOG DEV