JAVA / BRANCHING AND SWITCH EXPRESSIONS
switch as an expression that returns a value
Use switch in any value position (initializer, return, argument, operand) and predict the single value and single type it produces.
What you will learn
- Turn a value-picking if/else chain into one switch expression
- Place a switch expression in any value slot: initializer, return, argument, operand
- Read the ';' after the closing brace as the end of the statement, not the switch
- Predict a switch expression's type from the target type, not from one branch
Understanding switch as an expression that returns a value
A switch statement is an instruction: pick a branch and run it. A switch expression is a computation: pick a branch and hand back its value, which then flows into whatever was waiting for it. The difference is not the arrows or the braces, it is the position in the code — a switch sitting after '=', after 'return', or inside an argument list is being asked for a value, and javac compiles it under stricter rules. That position also explains the punctuation: the semicolon after the closing brace belongs to the assignment or return statement, because the switch is only one operand inside it.
Since the surrounding code is waiting for a value, every path through a switch expression must produce exactly one. That single guarantee explains most of what feels different about the expression form: the result variable can be final because it is assigned once, no branch can leave it holding a value another branch set, and control cannot escape with break, continue or return, because that would leave the surrounding expression with nothing to use. Read the whole construct as one arrow: selector in, value out, nothing else happening on the way through.
The value that comes out has one type for the whole switch, not one type per branch. In an assignment or argument position the target type decides: declare the result double and an int branch is widened, declare it Object and an int branch is boxed. Used as a bare operand, as in 4 * switch (...), there is no target to guide it, so the type comes from the branch values themselves, which is where mixed int and long branches, or String and Integer branches under var, hand you a type you did not intend. When such a switch refuses to compile, asking what type each branch is being forced into locates the problem faster than rereading the labels.
public class HttpStatus {
static String describe(int status) {
return switch (status) {
case 200, 201, 204 -> "success";
case 301, 302 -> "redirect";
case 400, 401, 403, 404 -> "client error";
case 500, 503 -> "server error";
default -> "unknown status " + status;
};
}
public static void main(String[] args) {
int[] codes = { 200, 302, 404, 503, 418 };
for (int code : codes) {
System.out.println(code + ": " + describe(code));
}
int seen = 503;
int retryAfter = switch (seen) {
case 429, 503 -> 5;
default -> 0;
};
System.out.println("retry after " + retryAfter + "s");
}
}A switch expression evaluates to exactly one value of one type, so it can stand wherever a value can stand, and every branch is obliged to supply that value.
Worked examples
One type for the whole switch
Shows that the declared target type, not the individual branches, fixes the type of the value that comes out.
public class ResultType {
public static void main(String[] args) {
int level = 2;
double factor = switch (level) {
case 1 -> 1;
case 2 -> 1.5;
default -> 2;
};
System.out.println("factor: " + factor);
Object tag = switch (level) {
case 1 -> "one";
case 2 -> 2;
default -> null;
};
System.out.println("tag class: " + tag.getClass().getName());
}
}Example explained
Line 1Declaring factor as double makes the whole switch a double expression, so the int literal in case 1 is widened instead of clashing with 1.5.
Line 2Only the matching branch is evaluated; case 2 supplies 1.5 and the other two branch values are never computed.
Line 3With Object as the target type the int 2 is boxed, which is why getClass() reports java.lang.Integer.
Line 4Both switches prove the same point: one type is chosen for the result, and each branch value must convert to it.
Returning the switch itself
Uses a switch expression as the entire body of a return statement, so no local variable is involved.
public class Coins {
enum Coin { PENNY, NICKEL, DIME, QUARTER }
static int cents(Coin coin) {
return switch (coin) {
case PENNY -> 1;
case NICKEL -> 5;
case DIME -> 10;
case QUARTER -> 25;
};
}
public static void main(String[] args) {
Coin[] purse = { Coin.QUARTER, Coin.DIME, Coin.DIME, Coin.PENNY };
int total = 0;
for (Coin coin : purse) {
total += cents(coin);
}
System.out.println("coins: " + purse.length + ", total: " + total + " cents");
System.out.println("a " + Coin.NICKEL + " alone: " + cents(Coin.NICKEL) + " cents");
}
}Example explained
Line 1return switch (coin) { ... }; hands the expression's value straight back, so there is no temporary variable that a branch could forget to set.
Line 2Each call evaluates the switch once and yields exactly one int, which total += then accumulates.
Line 3Listing all four constants of Coin means every possible selector has a branch, so the method always has something to return.
Line 4The method reads as a function from Coin to int, which is the shape that suits the expression form.
The value used in place
Puts switch expressions in an argument slot and in an arithmetic operand slot rather than in an assignment.
public class Banner {
public static void main(String[] args) {
String size = "large";
System.out.println("=".repeat(switch (size) {
case "small" -> 6;
case "medium" -> 12;
case "large" -> 20;
default -> 3;
}));
int width = 4 * switch (size) {
case "small" -> 1;
case "medium" -> 2;
default -> 3;
};
System.out.println("width: " + width);
}
}Example explained
Line 1The first switch occupies the int parameter of String.repeat, so the branch value 20 is what repeat actually receives.
Line 24 * switch (...) uses a switch as an operand; there is no assignment target here, so the type comes from the branch values, all int.
Line 3The second switch has no "large" label, so it takes default and produces 3, giving width 12 independently of the first switch.
Line 4Both forms are legal, but a switch buried in a bigger expression is hard to scan; naming the value in a local usually reads better.
Important notes
Switch expressions are standard from Java 14 onward; compiling the same file against an older source level fails at the arrow, so check the language level your editor is using.
A switch expression is allowed inside a larger expression, but readability drops quickly; assign it to a well-named local as soon as the branch values are more than a token long.
Common mistakes
Omitting the semicolon after the closing brace, as in int x = switch (n) { ... } with nothing following: the braces end the switch, not the statement, and javac reports ';' expected.
Trying to escape a branch with break or return; javac rejects it with a message about attempting to break or return out of a switch expression, because the value the surrounding code is waiting for would never arrive.
Putting a void call in a branch of a value-producing switch, such as case 1 -> System.out.println("one");, which fails to compile because println gives back nothing to assign.
Try it yourself
Change, predict, then run
In a browser editor write double cost = switch (zone) { ... }; with branches for "local", "national" and "intl" plus a default, then print the cost for those three zones and for an unknown zone like "moon".
Open the Java workspaceCheck your understanding
A branch of a switch expression tries to leave the method early with return, and javac refuses. What is the actual reason?
- The switch owes a value to the code around it, and returning would abandon that value
- return is only legal in colon-style branches; arrow branches forbid statements entirely
- The method's return type and the switch's type are inferred separately and can never match
- return only works in switch statements because arrow branches execute in their own stack frame
Show answer
The switch is in the middle of computing a value for an initializer, argument or return statement, so jumping out of the method would leave that expression with nothing to evaluate to; the language forbids it for that reason. Option 2 is tempting because arrows and jump statements rarely appear together, but a colon-style switch expression rejects return just as firmly, and arrow branches may contain a whole block of statements.