Guards in Switch (When Clause)
A guard is an extra condition attached to a case pattern with the keyword when. A case such as case Student s when s.marks() >= 90 matches only if the value is a Student and the condition is true. A pattern with a guard is called a guarded pattern. Guards let one switch express "this type, but only in this situation" without nesting an if inside the case. In this lesson, you will learn how to write guards, why their order matters, and how they affect the compiler's dominance and exhaustiveness checks.
Syntax
case Type variable when booleanExpression -> body;The guard can use the pattern variable, because it runs only after the type test has succeeded. If the guard is false, the switch moves on and tries the next case.
Example: Grading Results
Let's see an example that grades students with guards, and also handles blank text:
public class Grades {
record Student(String name, int marks) { }
static String result(Object o) {
return switch (o) {
case Student s when s.marks() >= 90 ->
s.name() + ": distinction";
case Student s when s.marks() >= 35 -> s.name() + ": pass";
case Student s -> s.name() + ": fail";
case String text when text.isBlank() -> "blank text";
case String text -> "text: " + text;
default -> "unknown";
};
}
public static void main(String[] args) {
System.out.println(result(new Student("Navika", 95)));
System.out.println(result(new Student("Srihitha", 72)));
System.out.println(result(new Student("Advitha", 20)));
System.out.println(result(" "));
System.out.println(result("hi"));
}
}Navika: distinction
Srihitha: pass
Advitha: fail
blank text
text: hiExplanation: For Srihitha (72), the first case's type test succeeds but its guard 72 >= 90 is false, so the switch tries the second case, whose guard 72 >= 35 is true. For Advitha (20), both guards fail, and the unguarded case Student s catches the rest. The pattern variable may have the same name, s, in every case, because each case has its own scope.
Order: Guarded Cases Before Unguarded Ones
A guarded case does not dominate a later case of the same type, because it doesn't match every value of that type. But an unguarded case dominates every later case of the same type, guarded or not:
return switch (o) {
case String s when s.length() > 3 -> "long"; // fine
case String s -> "short"; // fine
case String s when s.isEmpty() -> "empty"; // never reached
default -> "other";
};E6.java:6: error: this case label is dominated by a preceding case label
case String s when s.isEmpty() -> "empty";
^
1 errorThe rule of thumb: for each type, write the narrowest guards first, then the plain case last.
A constant case may follow a guarded case of the same type. Here 0 reaches case 0, because the guard i > 0 was false:
public class Sign {
static String sign(Integer n) {
return switch (n) {
case Integer i when i > 0 -> "positive";
case 0 -> "zero";
case Integer i -> "negative";
};
}
public static void main(String[] args) {
System.out.println(sign(7));
System.out.println(sign(0));
System.out.println(sign(-3));
}
}positive
zero
negativeGuards Don't Count for Exhaustiveness
The compiler can't know which values a guard will accept, so a guarded case never counts toward covering a type. With a sealed interface of Circle and Square, this switch isn't exhaustive, because big circles are covered but small ones aren't:
return switch (s) {
case Circle c when c.r() > 10 -> "big circle";
case Square q -> "square";
};G2.java:6: error: the switch expression does not cover all possible input values
return switch (s) {
^
1 errorAdd an unguarded case Circle c after the guarded one to fix it.
Rules for Guard Expressions
| Rule | Error if broken |
|---|---|
The guard must be a boolean expression | incompatible types |
| Local variables used in the guard must be final or effectively final | "local variables referenced from a guard must be final or effectively final" |
The guard can't be the constant false | "this case label has a guard that is a constant expression with value 'false'" |
| The guard can use the case's pattern variables | (allowed) |
The second rule is the same one lambdas follow. For example, this fails because limit is changed by limit++:
G4.java:6: error: local variables referenced from a guard must be final or effectively final
case String s when s.length() > limit -> "long";
^
1 errorCommon Mistakes
Putting the unguarded case first. Later guarded cases of the same type become unreachable.
Expecting guarded cases to make a switch exhaustive. Always finish with an unguarded case or default.
Changing a local variable that a guard uses. Copy it into a new final variable first.
Using if inside the case body instead of a guard. It works, but guards keep each situation as its own visible case.
Interview Corner
What is a guard in a switch pattern?
A guard is a boolean condition added to a case pattern with when, such as case String s when s.isBlank(). The case matches only if the pattern matches and the guard is true; otherwise the switch continues with the next case. The guard can use the pattern's variables, but local variables must be effectively final, and the guard can't be the constant false. A guarded case doesn't dominate later cases of the same type and doesn't count toward exhaustiveness, so it is usually followed by an unguarded case for that type or by default.
Practice
- Easy: Write a switch over
Integerthat returns "even" and "odd" using guards, with0handled by its own constant case. - Harder: Write a switch over
Objectthat classifiesStringvalues as "empty", "short" (under 5 characters) and "long", andList<?>values as "empty list" and "non-empty list". Make sure the order passes the dominance check.
Key Takeaways
Was this tutorial helpful?