When validating input or applying business rules, you often need both type matching and additional conditions. Guarded patterns combine type checking with boolean constraints using the when keyword, eliminating nested if statements inside case blocks.

Basic Syntax

obj
Basic.java
Replay: real traced execution (multi-file project)
public class Basic {
    public static void main(String[] args) {
        Object obj = "hello";
        switch (obj) {
            case String s when s.length() > 5 ->
                System.out.println("Long string: " + s);
            case String s ->
                System.out.println("Short string: " + s);
            case Integer i when i > 0 ->
                System.out.println("Positive: " + i);
            case Integer i ->
                System.out.println("Non-positive: " + i);
            default ->
                System.out.println("Other type");
        }
    }
}
public class Basic {
    public static void main(String[] args) {
        Object obj = "hello world";
        switch (obj) {
            case String s when s.length() > 5 ->
                System.out.println("Long string: " + s);
            case String s ->
                System.out.println("Short string: " + s);
            case Integer i when i > 0 ->
                System.out.println("Positive: " + i);
            case Integer i ->
                System.out.println("Non-positive: " + i);
            default ->
                System.out.println("Other type");
        }
    }
}
public class Basic {
    public static void main(String[] args) {
        Object obj = 7;
        switch (obj) {
            case String s when s.length() > 5 ->
                System.out.println("Long string: " + s);
            case String s ->
                System.out.println("Short string: " + s);
            case Integer i when i > 0 ->
                System.out.println("Positive: " + i);
            case Integer i ->
                System.out.println("Non-positive: " + i);
            default ->
                System.out.println("Other type");
        }
    }
}
public class Basic {
    public static void main(String[] args) {
        Object obj = -2;
        switch (obj) {
            case String s when s.length() > 5 ->
                System.out.println("Long string: " + s);
            case String s ->
                System.out.println("Short string: " + s);
            case Integer i when i > 0 ->
                System.out.println("Positive: " + i);
            case Integer i ->
                System.out.println("Non-positive: " + i);
            default ->
                System.out.println("Other type");
        }
    }
}
  1. obj ← hello

    1public class Basic {2    public static void main(String[] args) {3        Object obj→ hello = "hello"; //@obj="hello", "hello world", 7, -24        switch (obj) {
  1. obj ← hello world

    1public class Basic {2    public static void main(String[] args) {3        Object obj→ hello world = "hello world";4        switch (obj) {
  1. obj ← 7

    1public class Basic {2    public static void main(String[] args) {3        Object obj→ 7 = 7;4        switch (obj) {
  1. obj ← -2

    1public class Basic {2    public static void main(String[] args) {3        Object obj→ -2 = -2;4        switch (obj) {
when_clause A `when` clause adds a boolean condition that must be true for the pattern to match, evaluated after the type pattern succeeds.

Refinement Patterns

Refinement.java
Replay: real traced execution (multi-file project)
public class Refinement {
    static String describeNumber(Object obj) {
        return switch (obj) {
            case Integer i when i == 0 -> "Zero";
            case Integer i when i > 0 && i < 10 -> "Small positive";
            case Integer i when i >= 10 && i < 100 -> "Medium positive";
            case Integer i when i >= 100 -> "Large positive";
            case Integer i when i < 0 && i > -10 -> "Small negative";
            case Integer i when i <= -10 -> "Large negative";
            case Double d when d > 0 -> "Positive decimal";
            case Double d when d < 0 -> "Negative decimal";
            case Double d -> "Zero decimal";
            default -> "Not a number";
        };
    }

    public static void main(String[] args) {
        System.out.println(describeNumber(0));
        System.out.println(describeNumber(5));
        System.out.println(describeNumber(50));
        System.out.println(describeNumber(500));
        System.out.println(describeNumber(-5));
        System.out.println(describeNumber(-50));
        System.out.println(describeNumber(3.14));
    }
}
  1. public static void main(String[] args)

    17public static void main(String[] args) {18    System.out.println(describeNumber(0));19    System.out.println(describeNumber(5));
  2. static String describeNumber(Object obj)

    pass 1 of 7
    1public class Refinement {2    static String describeNumber(Object obj0) {3        return switch (obj) {4            case Integer i when i == 0 -> "Zero";5            case Integer i when i > 0 && i < 10 -> "Small positive";6            case Integer i when i >= 10 && i < 100 -> "Medium positive";7            case Integer i when i >= 100 -> "Large positive";8            case Integer i when i < 0 && i > -10 -> "Small negative";9            case Integer i when i <= -10 -> "Large negative";10            case Double d when d > 0 -> "Positive decimal";11            case Double d when d < 0 -> "Negative decimal";12            case Double d -> "Zero decimal";13            default -> "Not a number";14        };15    }
    All 7 passes — pass 1 is the card above
    passobj
    10
    25
    350
    4500
    5-5
    6-50
    73.14
  3. System.out.println(describeNumber(0));

    17public static void main(String[] args) {18    System.out.println(describeNumber(0));19    System.out.println(describeNumber(5));20    System.out.println(describeNumber(50));
    outputZero
  4. System.out.println(describeNumber(5));

    18System.out.println(describeNumber(0));19System.out.println(describeNumber(5));20System.out.println(describeNumber(50));21System.out.println(describeNumber(500));
    outputSmall positive
  5. System.out.println(describeNumber(50));

    19System.out.println(describeNumber(5));20System.out.println(describeNumber(50));21System.out.println(describeNumber(500));22System.out.println(describeNumber(-5));
    outputMedium positive
  6. System.out.println(describeNumber(500));

    20System.out.println(describeNumber(50));21System.out.println(describeNumber(500));22System.out.println(describeNumber(-5));23System.out.println(describeNumber(-50));
    outputLarge positive
  7. System.out.println(describeNumber(-5));

    21System.out.println(describeNumber(500));22System.out.println(describeNumber(-5));23System.out.println(describeNumber(-50));24System.out.println(describeNumber(3.14));
    outputSmall negative
  8. System.out.println(describeNumber(-50));

    22    System.out.println(describeNumber(-5));23    System.out.println(describeNumber(-50));24    System.out.println(describeNumber(3.14));25}
    outputLarge negative
  9. System.out.println(describeNumber(3.14));

    23    System.out.println(describeNumber(-50));24    System.out.println(describeNumber(3.14));25}
    outputPositive decimal
refinement Multiple guarded cases for the same type create refinement patterns, checked in order from most to least specific.

Evaluation Order

  1. Pattern matching (type check + bind)
  2. Guard evaluation (when condition)
  3. If guard is true, execute the case
  4. Otherwise, try the next case

Null-Safe Guards

NullSafe.java
Replay: real traced execution (multi-file project)
public class NullSafe {
    static String process(String s) {
        return switch (s) {
            case null -> "null value";
            case String str when str.isEmpty() -> "empty string";
            case String str when str.isBlank() -> "blank string";
            case String str when str.length() < 5 -> "short: " + str;
            case String str -> "normal: " + str;
        };
    }

    public static void main(String[] args) {
        System.out.println(process(null));
        System.out.println(process(""));
        System.out.println(process("   "));
        System.out.println(process("hi"));
        System.out.println(process("hello world"));
    }
}
  1. public static void main(String[] args)

    12public static void main(String[] args) {13    System.out.println(process(null));14    System.out.println(process(""));
  2. static String process(String s)

    pass 1 of 5
    1public class NullSafe {2    static String process(String snull) {3        return switch (s) {4            case null -> "null value";5            case String str when str.isEmpty() -> "empty string";6            case String str when str.isBlank() -> "blank string";7            case String str when str.length() < 5 -> "short: " + str;8            case String str -> "normal: " + str;9        };10    }
    All 5 passes — pass 1 is the card above
    passs
    1null
    2(empty)
    3
    4hi
    5hello world
  3. System.out.println(process(null));

    12public static void main(String[] args) {13    System.out.println(process(null));14    System.out.println(process(""));15    System.out.println(process("   "));
    outputnull value
  4. System.out.println(process(""));

    13System.out.println(process(null));14System.out.println(process(""));15System.out.println(process("   "));16System.out.println(process("hi"));
    outputempty string
  5. System.out.println(process(" "));

    14System.out.println(process(""));15System.out.println(process("   "));16System.out.println(process("hi"));17System.out.println(process("hello world"));
    outputblank string
  6. System.out.println(process("hi"));

    15    System.out.println(process("   "));16    System.out.println(process("hi"));17    System.out.println(process("hello world"));18}
    outputshort: hi
  7. System.out.println(process("hello world"));

    16    System.out.println(process("hi"));17    System.out.println(process("hello world"));18}
    outputnormal: hello world

Guards with Record Patterns

sample
Records.java
Replay: real traced execution (multi-file project)
public class Records {
    record Point(int x, int y) {}

    static String classify(Point p) {
        return switch (p) {
            case Point(int x, int y) when x == 0 && y == 0 -> "Origin";
            case Point(int x, int y) when x == y -> "On diagonal (y=x)";
            case Point(int x, int y) when x == -y -> "On anti-diagonal (y=-x)";
            case Point(int x, int y) when x == 0 -> "On Y-axis";
            case Point(int x, int y) when y == 0 -> "On X-axis";
            case Point(int x, int y) when x > 0 && y > 0 -> "Quadrant I";
            case Point(int x, int y) when x < 0 && y > 0 -> "Quadrant II";
            case Point(int x, int y) when x < 0 && y < 0 -> "Quadrant III";
            case Point(int x, int y) -> "Quadrant IV";
        };
    }

    public static void main(String[] args) {
        Point sample = new Point(0, 0);

        System.out.println(classify(sample));
        System.out.println(classify(new Point(3, 3)));
        System.out.println(classify(new Point(3, -3)));
        System.out.println(classify(new Point(0, 5)));
        System.out.println(classify(new Point(3, 4)));
    }
}
public class Records {
    record Point(int x, int y) {}

    static String classify(Point p) {
        return switch (p) {
            case Point(int x, int y) when x == 0 && y == 0 -> "Origin";
            case Point(int x, int y) when x == y -> "On diagonal (y=x)";
            case Point(int x, int y) when x == -y -> "On anti-diagonal (y=-x)";
            case Point(int x, int y) when x == 0 -> "On Y-axis";
            case Point(int x, int y) when y == 0 -> "On X-axis";
            case Point(int x, int y) when x > 0 && y > 0 -> "Quadrant I";
            case Point(int x, int y) when x < 0 && y > 0 -> "Quadrant II";
            case Point(int x, int y) when x < 0 && y < 0 -> "Quadrant III";
            case Point(int x, int y) -> "Quadrant IV";
        };
    }

    public static void main(String[] args) {
        Point sample = new Point(3, 3);

        System.out.println(classify(sample));
        System.out.println(classify(new Point(3, 3)));
        System.out.println(classify(new Point(3, -3)));
        System.out.println(classify(new Point(0, 5)));
        System.out.println(classify(new Point(3, 4)));
    }
}
public class Records {
    record Point(int x, int y) {}

    static String classify(Point p) {
        return switch (p) {
            case Point(int x, int y) when x == 0 && y == 0 -> "Origin";
            case Point(int x, int y) when x == y -> "On diagonal (y=x)";
            case Point(int x, int y) when x == -y -> "On anti-diagonal (y=-x)";
            case Point(int x, int y) when x == 0 -> "On Y-axis";
            case Point(int x, int y) when y == 0 -> "On X-axis";
            case Point(int x, int y) when x > 0 && y > 0 -> "Quadrant I";
            case Point(int x, int y) when x < 0 && y > 0 -> "Quadrant II";
            case Point(int x, int y) when x < 0 && y < 0 -> "Quadrant III";
            case Point(int x, int y) -> "Quadrant IV";
        };
    }

    public static void main(String[] args) {
        Point sample = new Point(3, 4);

        System.out.println(classify(sample));
        System.out.println(classify(new Point(3, 3)));
        System.out.println(classify(new Point(3, -3)));
        System.out.println(classify(new Point(0, 5)));
        System.out.println(classify(new Point(3, 4)));
    }
}
  1. sample ← Point[x=0, y=0]

    18public static void main(String[] args) {19    Point sample→ Point[x=0, y=0] = new Point(0, 0); //@sample=new Point(0, 0), new Point(3, 3), new Point(3, 4)2021    System.out.println(classify(samplePoint[x=0, y=0]));22    System.out.println(classify(new Point(3, 3)));
  2. static String classify(Point p)

    pass 1 of 5
    4static String classify(Point pPoint[x=0, y=0]) {5    return switch (p) {6        case Point(int x, int y) when x == 0 && y == 0 -> "Origin";7        case Point(int x, int y) when x == y -> "On diagonal (y=x)";8        case Point(int x, int y) when x == -y -> "On anti-diagonal (y=-x)";9        case Point(int x, int y) when x == 0 -> "On Y-axis";10        case Point(int x, int y) when y == 0 -> "On X-axis";11        case Point(int x, int y) when x > 0 && y > 0 -> "Quadrant I";12        case Point(int x, int y) when x < 0 && y > 0 -> "Quadrant II";13        case Point(int x, int y) when x < 0 && y < 0 -> "Quadrant III";14        case Point(int x, int y) -> "Quadrant IV";15    };16}
    All 5 passes — pass 1 is the card above
    passp
    1Point[x=0, y=0]
    2Point[x=3, y=3]
    3Point[x=3, y=-3]
    4Point[x=0, y=5]
    5Point[x=3, y=4]
  3. System.out.println(classify(sample));

    21System.out.println(classify(samplePoint[x=0, y=0]));22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));
    outputOrigin
  4. System.out.println(classify(new Point(3, 3)));

    21System.out.println(classify(sample));22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));24System.out.println(classify(new Point(0, 5)));
    outputOn diagonal (y=x)
  5. System.out.println(classify(new Point(3, -3)));

    22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));24System.out.println(classify(new Point(0, 5)));25System.out.println(classify(new Point(3, 4)));
    outputOn anti-diagonal (y=-x)
  6. System.out.println(classify(new Point(0, 5)));

    23    System.out.println(classify(new Point(3, -3)));24    System.out.println(classify(new Point(0, 5)));25    System.out.println(classify(new Point(3, 4)));26}
    outputOn Y-axis
  7. System.out.println(classify(new Point(3, 4)));

    24    System.out.println(classify(new Point(0, 5)));25    System.out.println(classify(new Point(3, 4)));26}
    outputQuadrant I
  1. sample ← Point[x=3, y=3]

    18public static void main(String[] args) {19    Point sample→ Point[x=3, y=3] = new Point(3, 3);2021    System.out.println(classify(samplePoint[x=3, y=3]));22    System.out.println(classify(new Point(3, 3)));
  2. static String classify(Point p)

    pass 1 of 5
    4static String classify(Point pPoint[x=3, y=3]) {5    return switch (p) {6        case Point(int x, int y) when x == 0 && y == 0 -> "Origin";7        case Point(int x, int y) when x == y -> "On diagonal (y=x)";8        case Point(int x, int y) when x == -y -> "On anti-diagonal (y=-x)";9        case Point(int x, int y) when x == 0 -> "On Y-axis";10        case Point(int x, int y) when y == 0 -> "On X-axis";11        case Point(int x, int y) when x > 0 && y > 0 -> "Quadrant I";12        case Point(int x, int y) when x < 0 && y > 0 -> "Quadrant II";13        case Point(int x, int y) when x < 0 && y < 0 -> "Quadrant III";14        case Point(int x, int y) -> "Quadrant IV";15    };16}
    All 5 passes — pass 1 is the card above
    passp
    1Point[x=3, y=3]
    2Point[x=3, y=3]
    3Point[x=3, y=-3]
    4Point[x=0, y=5]
    5Point[x=3, y=4]
  3. System.out.println(classify(sample));

    21System.out.println(classify(samplePoint[x=3, y=3]));22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));
    outputOn diagonal (y=x)
  4. System.out.println(classify(new Point(3, 3)));

    21System.out.println(classify(sample));22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));24System.out.println(classify(new Point(0, 5)));
    outputOn diagonal (y=x)
  5. System.out.println(classify(new Point(3, -3)));

    22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));24System.out.println(classify(new Point(0, 5)));25System.out.println(classify(new Point(3, 4)));
    outputOn anti-diagonal (y=-x)
  6. System.out.println(classify(new Point(0, 5)));

    23    System.out.println(classify(new Point(3, -3)));24    System.out.println(classify(new Point(0, 5)));25    System.out.println(classify(new Point(3, 4)));26}
    outputOn Y-axis
  7. System.out.println(classify(new Point(3, 4)));

    24    System.out.println(classify(new Point(0, 5)));25    System.out.println(classify(new Point(3, 4)));26}
    outputQuadrant I
  1. sample ← Point[x=3, y=4]

    18public static void main(String[] args) {19    Point sample→ Point[x=3, y=4] = new Point(3, 4);2021    System.out.println(classify(samplePoint[x=3, y=4]));22    System.out.println(classify(new Point(3, 3)));
  2. static String classify(Point p)

    pass 1 of 5
    4static String classify(Point pPoint[x=3, y=4]) {5    return switch (p) {6        case Point(int x, int y) when x == 0 && y == 0 -> "Origin";7        case Point(int x, int y) when x == y -> "On diagonal (y=x)";8        case Point(int x, int y) when x == -y -> "On anti-diagonal (y=-x)";9        case Point(int x, int y) when x == 0 -> "On Y-axis";10        case Point(int x, int y) when y == 0 -> "On X-axis";11        case Point(int x, int y) when x > 0 && y > 0 -> "Quadrant I";12        case Point(int x, int y) when x < 0 && y > 0 -> "Quadrant II";13        case Point(int x, int y) when x < 0 && y < 0 -> "Quadrant III";14        case Point(int x, int y) -> "Quadrant IV";15    };16}
    All 5 passes — pass 1 is the card above
    passp
    1Point[x=3, y=4]
    2Point[x=3, y=3]
    3Point[x=3, y=-3]
    4Point[x=0, y=5]
    5Point[x=3, y=4]
  3. System.out.println(classify(sample));

    21System.out.println(classify(samplePoint[x=3, y=4]));22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));
    outputQuadrant I
  4. System.out.println(classify(new Point(3, 3)));

    21System.out.println(classify(sample));22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));24System.out.println(classify(new Point(0, 5)));
    outputOn diagonal (y=x)
  5. System.out.println(classify(new Point(3, -3)));

    22System.out.println(classify(new Point(3, 3)));23System.out.println(classify(new Point(3, -3)));24System.out.println(classify(new Point(0, 5)));25System.out.println(classify(new Point(3, 4)));
    outputOn anti-diagonal (y=-x)
  6. System.out.println(classify(new Point(0, 5)));

    23    System.out.println(classify(new Point(3, -3)));24    System.out.println(classify(new Point(0, 5)));25    System.out.println(classify(new Point(3, 4)));26}
    outputOn Y-axis
  7. System.out.println(classify(new Point(3, 4)));

    24    System.out.println(classify(new Point(0, 5)));25    System.out.println(classify(new Point(3, 4)));26}
    outputQuadrant I

Benefits

  • Precise matching: combine type and value constraints
  • Cleaner code: avoid nested ifs
  • Readable: conditions are explicit in the pattern

Using Methods in Guards

Methods.java
Replay: real traced execution (multi-file project)
public class Methods {
    static boolean isPalindrome(String s) {
        return s.equals(new StringBuilder(s).reverse().toString());
    }

    static String analyze(Object obj) {
        return switch (obj) {
            case String s when s.isEmpty() -> "Empty string";
            case String s when isPalindrome(s) -> "Palindrome: " + s;
            case String s when s.matches("[0-9]+") -> "Numeric string";
            case String s when s.matches("[a-zA-Z]+") -> "Alphabetic string";
            case String s -> "Other string: " + s;
            default -> "Not a string";
        };
    }

    public static void main(String[] args) {
        System.out.println(analyze(""));
        System.out.println(analyze("racecar"));
        System.out.println(analyze("12345"));
        System.out.println(analyze("hello"));
        System.out.println(analyze("hello123"));
    }
}
  1. public static void main(String[] args)

    17public static void main(String[] args) {18    System.out.println(analyze(""));19    System.out.println(analyze("racecar"));
  2. static String analyze(Object obj)

    pass 1 of 5
    6static String analyze(Object obj(empty)) {7    return switch (obj) {8        case String s when s.isEmpty() -> "Empty string";9        case String s when isPalindrome(s) -> "Palindrome: " + s;10        case String s when s.matches("[0-9]+") -> "Numeric string";11        case String s when s.matches("[a-zA-Z]+") -> "Alphabetic string";12        case String s -> "Other string: " + s;13        default -> "Not a string";14    };15}
    All 5 passes — pass 1 is the card above
    passobj
    1(empty)
    2racecar
    312345
    4hello
    5hello123
  3. System.out.println(analyze(""));

    17public static void main(String[] args) {18    System.out.println(analyze(""));19    System.out.println(analyze("racecar"));20    System.out.println(analyze("12345"));
    outputEmpty string
  4. static boolean isPalindrome(String s)

    pass 1 of 4
    1public class Methods {2    static boolean isPalindrome(String sracecar) {3        return s.equals(new StringBuilder(s).reverse().toString());4    }
    All 4 passes — pass 1 is the card above
    passs
    1racecar
    212345
    3hello
    4hello123
  5. System.out.println(analyze("racecar"));

    18System.out.println(analyze(""));19System.out.println(analyze("racecar"));20System.out.println(analyze("12345"));21System.out.println(analyze("hello"));
    outputPalindrome: racecar
  6. System.out.println(analyze("12345"));

    19System.out.println(analyze("racecar"));20System.out.println(analyze("12345"));21System.out.println(analyze("hello"));22System.out.println(analyze("hello123"));
    outputNumeric string
  7. System.out.println(analyze("hello"));

    20    System.out.println(analyze("12345"));21    System.out.println(analyze("hello"));22    System.out.println(analyze("hello123"));23}
    outputAlphabetic string
  8. System.out.println(analyze("hello123"));

    21    System.out.println(analyze("hello"));22    System.out.println(analyze("hello123"));23}
    outputOther string: hello123
method_guard Guards can call methods on the pattern variable, enabling complex validation logic.

Exercise: Practical.java

Build a request validator that uses guards to check authentication, rate limits, and payload validation