Pattern Matching
Guarded Patterns
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
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");
}
}
}
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) {
obj ← hello world
1public class Basic {2 public static void main(String[] args) {3 Object obj→ hello world = "hello world";4 switch (obj) {
obj ← 7
1public class Basic {2 public static void main(String[] args) {3 Object obj→ 7 = 7;4 switch (obj) {
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));
}
}
public static void main(String[] args)
17public static void main(String[] args) {18 System.out.println(describeNumber(0));19 System.out.println(describeNumber(5));static String describeNumber(Object obj)
pass 1 of 71public 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 pass obj1 0 2 5 3 50 4 500 5 -5 6 -50 7 3.14 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));outputZeroSystem.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 positiveSystem.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 positiveSystem.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 positiveSystem.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 negativeSystem.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 negativeSystem.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
- Pattern matching (type check + bind)
- Guard evaluation (
whencondition) - If guard is true, execute the case
- 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"));
}
}
public static void main(String[] args)
12public static void main(String[] args) {13 System.out.println(process(null));14 System.out.println(process(""));static String process(String s)
pass 1 of 51public 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 pass s1 null 2 (empty) 3 4 hi 5 hello world 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 valueSystem.out.println(process(""));
13System.out.println(process(null));14System.out.println(process(""));15System.out.println(process(" "));16System.out.println(process("hi"));outputempty stringSystem.out.println(process(" "));
14System.out.println(process(""));15System.out.println(process(" "));16System.out.println(process("hi"));17System.out.println(process("hello world"));outputblank stringSystem.out.println(process("hi"));
15 System.out.println(process(" "));16 System.out.println(process("hi"));17 System.out.println(process("hello world"));18}outputshort: hiSystem.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
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)));
}
}
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)));static String classify(Point p)
pass 1 of 54static 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 pass p1 Point[x=0, y=0] 2 Point[x=3, y=3] 3 Point[x=3, y=-3] 4 Point[x=0, y=5] 5 Point[x=3, y=4] 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)));outputOriginSystem.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)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)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-axisSystem.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
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)));static String classify(Point p)
pass 1 of 54static 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 pass p1 Point[x=3, y=3] 2 Point[x=3, y=3] 3 Point[x=3, y=-3] 4 Point[x=0, y=5] 5 Point[x=3, y=4] 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)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)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)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-axisSystem.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
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)));static String classify(Point p)
pass 1 of 54static 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 pass p1 Point[x=3, y=4] 2 Point[x=3, y=3] 3 Point[x=3, y=-3] 4 Point[x=0, y=5] 5 Point[x=3, y=4] 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 ISystem.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)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)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-axisSystem.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"));
}
}
public static void main(String[] args)
17public static void main(String[] args) {18 System.out.println(analyze(""));19 System.out.println(analyze("racecar"));static String analyze(Object obj)
pass 1 of 56static 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 pass obj1 (empty) 2 racecar 3 12345 4 hello 5 hello123 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 stringstatic boolean isPalindrome(String s)
pass 1 of 41public 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 pass s1 racecar 2 12345 3 hello 4 hello123 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: racecarSystem.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 stringSystem.out.println(analyze("hello"));
20 System.out.println(analyze("12345"));21 System.out.println(analyze("hello"));22 System.out.println(analyze("hello123"));23}outputAlphabetic stringSystem.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