Guards in Switch (When Clause)

Naveen AnamaneniUpdated · 6 min read

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

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:

Grades.java▶ Try it
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"));
    }
}
Output
Navika: distinction
Srihitha: pass
Advitha: fail
blank text
text: hi

Explanation: 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:

E6.java
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";
};
Compiler output
E6.java:6: error: this case label is dominated by a preceding case label
            case String s when s.isEmpty() -> "empty";
                 ^
1 error

The 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:

Sign.java▶ Try it
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));
    }
}
Output
positive
zero
negative

Guards 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:

G2.java
return switch (s) {
    case Circle c when c.r() > 10 -> "big circle";
    case Square q -> "square";
};
Compiler output
G2.java:6: error: the switch expression does not cover all possible input values
        return switch (s) {
               ^
1 error

Add an unguarded case Circle c after the guarded one to fix it.

Rules for Guard Expressions

RuleError if broken
The guard must be a boolean expressionincompatible 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++:

Compiler output
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 error

Common 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

  1. Easy: Write a switch over Integer that returns "even" and "odd" using guards, with 0 handled by its own constant case.
  2. Harder: Write a switch over Object that classifies String values as "empty", "short" (under 5 characters) and "long", and List<?> values as "empty list" and "non-empty list". Make sure the order passes the dominance check.

Quick check: Guards

4 questions
1What happens when a guard is false?
2Which order compiles?
3Does a guarded case count toward exhaustiveness?
4A guard uses a local variable that is later incremented. What happens?

Key Takeaways

Was this tutorial helpful?

Lesson 457 of 482