4.4 Switch Expressions and Pattern Matching (Java 14+)
Starting from Java 14, the traditional switch statement received a major upgrade. Now switch can be used not just as a statement but also as an expression that returns a value. In Java 21, pattern matching was added, making it even more powerful.
1. Problems with the Old switch Statement
The old switch was prone to fall-through bugs where forgetting a break would cause execution to run into the next case.
// Old style — risk of fall-through bugs
int day = 2;
String dayName;
switch (day) {
case 1:
dayName = "Monday";
break; // forgetting this causes execution to fall into the next case!
case 2:
dayName = "Tuesday";
break;
case 3:
dayName = "Wednesday";
break;
default:
dayName = "Other";
}
Also, assigning values to dayName required declaring the variable first and then assigning in each case, making the structure verbose.
2. Switch Expression (Java 14+) — Arrow (->) Syntax
The new arrow syntax makes each case act independently without break, and the expression can directly return a value.
// New style — concise and safe!
int day = 2;
String dayName = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
case 4 -> "Thursday";
case 5 -> "Friday";
case 6 -> "Saturday";
case 7 -> "Sunday";
default -> "Invalid day";
};
System.out.println(dayName); // Tuesday
Grouping Multiple Cases
int day = 6;
String type = switch (day) {
case 1, 2, 3, 4, 5 -> "Weekday"; // group cases with commas
case 6, 7 -> "Weekend";
default -> "Invalid day";
};
System.out.println(type); // Weekend
3. yield Keyword — Returning a Value from a Block
When multiple lines of code are needed after an arrow, use a { } block and return the value with the yield keyword.
int score = 85;
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8 -> {
System.out.println("Good job! Grade B.");
yield "B"; // use yield to return from a block
}
case 7 -> "C";
case 6 -> "D";
default -> {
System.out.println("Keep working harder!");
yield "F";
}
};
System.out.println("Grade: " + grade); // Grade: B
In traditional switch statements, break is used to exit. In switch expression blocks, yield is used to return a value. Note the difference between the two keywords.
4. Practical Patterns with Switch Expressions
Pattern 1: HTTP Status Code Handler
public class HttpStatusHandler {
static String getStatusMessage(int code) {
return switch (code) {
case 200 -> "OK - Request succeeded";
case 201 -> "Created - Resource created";
case 204 -> "No Content - No content";
case 400 -> "Bad Request - Invalid request";
case 401 -> "Unauthorized - Authentication required";
case 403 -> "Forbidden - Access denied";
case 404 -> "Not Found - Resource not found";
case 500 -> "Internal Server Error - Server error";
case 503 -> "Service Unavailable - Service unavailable";
default -> {
if (code >= 100 && code < 200) yield "1xx - Informational";
else if (code >= 200 && code < 300) yield "2xx - Success";
else if (code >= 300 && code < 400) yield "3xx - Redirection";
else if (code >= 400 && code < 500) yield "4xx - Client error";
else if (code >= 500 && code < 600) yield "5xx - Server error";
else yield "Unknown status code";
}
};
}
public static void main(String[] args) {
System.out.println(getStatusMessage(200)); // OK - Request succeeded
System.out.println(getStatusMessage(404)); // Not Found - Resource not found
System.out.println(getStatusMessage(502)); // 5xx - Server error
}
}
Pattern 2: enum with Switch Expression
public class SeasonInfo {
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
public static void main(String[] args) {
Season current = Season.SUMMER;
String description = switch (current) {
case SPRING -> "Spring: warm with blooming flowers.";
case SUMMER -> "Summer: hot and humid.";
case AUTUMN -> "Autumn: cool with changing leaves.";
case WINTER -> "Winter: cold with snow.";
// no default needed — all enum values are handled
};
int avgTemp = switch (current) {
case SPRING -> 15;
case SUMMER -> 30;
case AUTUMN -> 18;
case WINTER -> -2;
};
System.out.println(description);
System.out.println("Average temperature: " + avgTemp + " C");
}
}
If all enum constants are handled in cases, default can be omitted. If a new value is later added to the enum, the compiler will warn about unhandled cases. This is the greatest advantage of combining enum with switch expressions.
Pattern 3: Calculator
public class Calculator {
static double calculate(double a, char op, double b) {
return switch (op) {
case '+' -> a + b;
case '-' -> a - b;
case '*' -> a * b;
case '/' -> {
if (b == 0) {
System.out.println("Cannot divide by zero.");
yield Double.NaN;
}
yield a / b;
}
default -> {
System.out.println("Unsupported operator: " + op);
yield Double.NaN;
}
};
}
public static void main(String[] args) {
System.out.println(calculate(10, '+', 3)); // 13.0
System.out.println(calculate(10, '/', 0)); // Cannot divide by zero. / NaN
System.out.println(calculate(7, '*', 6)); // 42.0
}
}
5. instanceof Pattern Matching (Java 16+)
Previously, after an instanceof check, a separate cast was required. Now the check, cast, and variable declaration can all be done at once.
// Old style (3 steps)
Object obj = "Hello, Java!";
if (obj instanceof String) { // 1. check type
String s = (String) obj; // 2. manual cast
System.out.println(s.length()); // 3. use
}
// Pattern matching (Java 16+) — all in one step!
if (obj instanceof String s) { // check + cast + variable declaration at once!
System.out.println(s.length()); // use s directly
}
Practical Example: Calculate Area of Various Shapes
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
public class PatternMatchingExample {
static double getArea(Shape shape) {
if (shape instanceof Circle c) {
return Math.PI * c.radius() * c.radius();
} else if (shape instanceof Rectangle r) {
return r.width() * r.height();
} else if (shape instanceof Triangle t) {
return 0.5 * t.base() * t.height();
}
throw new IllegalArgumentException("Unknown shape");
}
public static void main(String[] args) {
System.out.println(getArea(new Circle(5))); // 78.53...
System.out.println(getArea(new Rectangle(4, 6))); // 24.0
System.out.println(getArea(new Triangle(3, 8))); // 12.0
}
}
6. Pattern Matching with switch (Java 21)
In Java 21, type pattern matching in switch expressions is officially supported. You can handle each type without a chain of instanceof checks.
static String describe(Object obj) {
return switch (obj) {
case Integer i -> "Integer: " + i;
case Double d -> "Double: " + d;
case String s -> "String (length " + s.length() + "): " + s;
case int[] arr -> "int array (length " + arr.length + ")";
case null -> "null value";
default -> "Unknown type: " + obj.getClass().getName();
};
}
public static void main(String[] args) {
System.out.println(describe(42)); // Integer: 42
System.out.println(describe(3.14)); // Double: 3.14
System.out.println(describe("Hello")); // String (length 5): Hello
System.out.println(describe(null)); // null value
}
Guarded Patterns — Java 21
A when clause can be added to a case to apply extra conditions.
static String classifyNumber(Object obj) {
return switch (obj) {
case Integer i when i < 0 -> "Negative: " + i;
case Integer i when i == 0 -> "Zero";
case Integer i when i > 0 -> "Positive: " + i;
case Double d when d.isNaN() -> "NaN (not a number)";
default -> "Not an integer or double";
};
}
System.out.println(classifyNumber(-5)); // Negative: -5
System.out.println(classifyNumber(0)); // Zero
System.out.println(classifyNumber(42)); // Positive: 42
7. Choosing Between switch and if-else
| Situation | Recommended |
|---|---|
| Equality checks against specific values (3+) | switch expression |
Range conditions (>, <, >=) | if-else if |
Complex compound conditions (&&, ||) | if-else if |
| Branching on an enum | switch expression (default can be omitted) |
| Branching by type | switch pattern matching (Java 21) |
| Simple two-way choice | Ternary operator or if-else |
Things to watch out for with switch expressions:
- A switch expression must handle all possible cases. Without a
default, you get a compile error. String,enum, integer types, wrapper types, and from Java 21 allObjecttypes can be switched on.- The enum + switch expression combination is the most commonly used pattern in production. Adding a new constant to an enum causes the compiler to immediately flag unhandled cases.
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
Season season = Season.SUMMER;
int avgTemp = switch (season) {
case SPRING -> 15;
case SUMMER -> 30;
case AUTUMN -> 18;
case WINTER -> -2;
// no default needed — all enum values are handled
};