Why this matters
- Missing
breakis one of the oldest bug sources in C-family languages, and switch expressions remove the possibility entirely. - Exhaustiveness means adding an enum constant breaks the build rather than silently falling into
default. - Embedded JSON, SQL and HTML are everywhere, and escaped single-line strings make them unreadable and error-prone.
The old switch statement
String season;
switch (month) {
case DECEMBER:
case JANUARY:
case FEBRUARY:
season = "winter";
break; // forget this and you silently get "spring"
case MARCH:
case APRIL:
case MAY:
season = "spring";
break;
default:
season = "unknown";
}
Four problems: the variable must be declared outside and uninitialised, each case needs an explicit break, each
grouped label needs its own line, and the whole thing is a statement so it cannot be used inline.
Switch expressions
String season = switch (month) {
case DECEMBER, JANUARY, FEBRUARY -> "winter";
case MARCH, APRIL, MAY -> "spring";
case JUNE, JULY, AUGUST -> "summer";
case SEPTEMBER, OCTOBER, NOVEMBER -> "autumn";
};
What the arrow form changes
- No fall-through. Each arm runs only its own branch, so
breakis gone. - Multiple labels per arm, comma-separated.
- It is an expression, so it can be assigned, returned, or passed as an argument.
- Exhaustiveness is required when it produces a value. Over an enum, covering every constant is enough and
defaultis unnecessary.
// Returned directly
int days(Month month) {
return switch (month) {
case FEBRUARY -> 28;
case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
default -> 31;
};
}
// Used inline
log.info("handling a {} request", switch (method) {
case GET, HEAD -> "read";
case POST, PUT, PATCH, DELETE -> "write";
});
Multi-line arms with yield
When an arm needs more than one statement, use a block and yield the value.
int score = switch (grade) {
case "A" -> 4;
case "B" -> 3;
case "C" -> {
audit.record("borderline grade");
yield 2; // yield, not return
}
default -> throw new IllegalArgumentException("unknown grade: " + grade);
};
The colon form still exists
The old syntax also works as an expression, using yield instead of assignment. It is legal but rarely worth it.
int value = switch (code) {
case 1: yield 10;
case 2: yield 20;
default: yield 0;
};
| Aspect | Arrow form | Colon form |
|---|---|---|
| Fall-through | Impossible | Default behaviour — needs break |
| Scope | Each arm has its own scope | All cases share one scope |
| Value | The expression after -> , or yield in a block | yield only |
| Grouping labels | case A, B, C -> | Stacked case labels |
| Recommendation | Use this | Only when maintaining old code |
Fall-through
Arrow formImpossibleColon formDefault behaviour — needs breakScope
Arrow formEach arm has its own scopeColon formAll cases share one scopeValue
Arrow formThe expression after -> , or yield in a blockColon formyield onlyGrouping labels
Arrow formcase A, B, C ->Colon formStacked case labelsRecommendation
Arrow formUse thisColon formOnly when maintaining old code
The shared scope in the colon form is a real hazard: a variable declared in one case is visible in the next.
Switch on what
switch (intValue) { } // int, short, char, byte and their wrappers
switch (enumValue) { } // enum — exhaustiveness checked
switch (stringValue) { } // String, since Java 7
switch (object) { } // any type, with type patterns — Java 21
Patterns in switch are covered separately; the arrow form and exhaustiveness described here are what make them
possible.
Text blocks
// Escaped — the structure is invisible
String json = "{\n \"id\": 42,\n \"name\": \"Ada\",\n \"tags\": [\"a\", \"b\"]\n}";
// Text block — the structure is the structure
String json = """
{
"id": 42,
"name": "Ada",
"tags": ["a", "b"]
}
""";
A text block starts with """ followed by a line terminator, and ends with """. The result is a perfectly
ordinary String — there is no new type, and interning and equals work exactly as before.
Incidental indentation
This is the rule that matters. The compiler finds the minimum indentation across all non-blank lines and the closing delimiter line, then strips that much from every line.
String sql = """
SELECT id, name
FROM users
WHERE active = true
""";
// Produces:
// SELECT id, name
// FROM users
// WHERE active = true
The eight leading spaces are incidental and removed; the relative indentation of FROM and WHERE is essential
and kept.
// Moving the closing delimiter left preserves more indentation
String indented = """
line one
line two
""";
// Produces " line one\n line two\n" — the delimiter at column 0 strips nothing
Line endings and escapes
// A trailing \ suppresses the newline — useful for long single-line text
String oneLine = """
this is all \
one single line\
""";
// "this is all one single line"
// \s preserves trailing whitespace
String padded = """
value \s
""";
// Quotes need no escaping
String quoted = """
He said "hello" and left.
""";
Where text blocks earn their place
String query = """
SELECT o.id, o.total, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= ?
AND o.status = 'OPEN'
ORDER BY o.created_at DESC
""";
String template = """
<html>
<body>
<h1>%s</h1>
<p>%s</p>
</body>
</html>
""";
String page = template.formatted(title, body); // formatted() reads better than String.format here
Common misreadings
- "Switch expressions can fall through." The arrow form cannot. Only the colon form falls through.
- "
defaultis always required." Not when the switch covers every enum constant, or every permitted subtype of a sealed type. - "
yieldandreturnare the same."yieldgives a value to the switch;returnexits the method and is illegal in a switch expression. - "The colon form is deprecated." It still compiles; it is just worse.
- "A text block is a new kind of string." It is a
Stringliteral with different source syntax. - "All leading whitespace is kept." Common indentation is stripped, including based on the closing delimiter's position.
- "Text blocks support interpolation." They do not. Use
formatted(). - "Line endings follow the platform." Always
\n.
Quick recall
Everything you need if you only revisit this box.
- The arrow form removes fall-through, allows comma-separated labels, and makes switch an expression.
- A switch expression must be exhaustive — over an enum that means every constant, and then
defaultis unnecessary and adding a constant breaks the build. - Use a block with
yieldfor multi-statement arms;returnis illegal, and athrowarm produces no value. - The colon form shares one scope across cases, which is a hazard; prefer arrows.
- A text block opens with
"""plus a newline and produces an ordinaryString. - Incidental indentation is stripped, determined by the least-indented non-blank line and the closing delimiter — so position the closing
"""to control it. \at end of line joins lines;\spreserves a trailing space; quotes need no escaping; line endings are always\n.- No interpolation — use
formatted().
Test yourself
Answer these before moving on — recall is what makes it stick.