Pattern Matching

1. What Is Pattern Matching?


2. Pattern Matching with Regular Expressions

Regular expressions are another form of pattern matching, mainly used for finding patterns in text.

Example:

Pattern pattern = Pattern.compile("\\bflame\\b");
Matcher matcher = pattern.matcher(text);

while (matcher.find()) {
    System.out.println(matcher.group());
}

Here:

Pattern
   ↓
"flame"
   ↓
Search inside text
   ↓
Matcher finds matching occurrences

Useful classes:

Pattern
Matcher

3. Pattern Matching with instanceof

Since Java 16, instanceof can directly test a type and create a variable for the matched object.

Traditional approach

Before pattern matching:

if (o instanceof String) {
    String s = (String) o;
    System.out.println(s.length());
}

With pattern matching:

if (o instanceof String s) {
    System.out.println(s.length());
}

This:

o instanceof String s

means:

Is o a String?
       ↓
     Yes
       ↓
Create variable s containing that String

s is called a pattern variable.


4. Pattern Variables and Scope

The pattern variable only exists where Java knows that the pattern matched.

Example:

if (o instanceof String s) {
    System.out.println(s.length());
}

You can also combine it with &&:

if (o instanceof String s && !s.isEmpty()) {
    System.out.println(s);
}

Here s is available in the second condition because && evaluates it only after the first condition succeeds.

You can also use it after a condition that exits:

if (!(o instanceof String s)) {
    return;
}

System.out.println(s.length());

After the return, Java knows that o must be a String, so s is available.


5. Pattern Matching Makes equals() Cleaner

Traditional code often requires:

if (!(o instanceof Point)) {
    return false;
}

Point point = (Point) o;

return x == point.x && y == point.y;

Pattern matching simplifies this:

public boolean equals(Object o) {
    return o instanceof Point point
        && x == point.x
        && y == point.y;
}

So pattern matching combines:

type checking
     +
casting
     +
variable declaration

into one expression.


6. Pattern Matching with switch

Since Java 21, switch can match objects using type patterns.

Example:

String result = switch (o) {
    case Integer i -> "Integer: " + i;
    case Long l -> "Long: " + l;
    case Double d -> "Double: " + d;
    default -> "Other: " + o;
};

Instead of:

if (o instanceof Integer) {
    ...
} else if (o instanceof Long) {
    ...
} else if (o instanceof Double) {
    ...
}

the switch handles all the cases together.

The important idea is:

o
↓
Integer? → i
Long?    → l
Double?  → d
Other?   → default

The pattern variable is available inside its corresponding case:

case Integer i -> System.out.println(i);

7. Guarded Cases with when

A switch pattern can also have an additional condition using when.

Example:

String result = switch (o) {
    case String s when !s.isEmpty() -> "Non-empty: " + s;
    case String s -> "Empty string";
    default -> "Not a string";
};

The first case means:

Is it a String?
      +
Is it NOT empty?

So when adds an additional condition to the pattern.


8. Record Patterns

A record pattern allows you to match a record and extract its components directly.

Suppose:

record Point(int x, int y) {}

Without a record pattern:

if (o instanceof Point p) {
    int x = p.x();
    int y = p.y();
}

With a record pattern:

if (o instanceof Point(int x, int y)) {
    System.out.println(x);
    System.out.println(y);
}

The record is automatically deconstructed:

Point
 ↓
(int x, int y)
 ↓
x and y are directly available

This is especially useful when working with records containing multiple components.


9. Pattern Matching in switch + Records

Record patterns can also be used inside switch.

String describe(Object obj) {
    return switch (obj) {
        case Point(int x, int y) ->
            "Point at " + x + ", " + y;

        default ->
            "Unknown object";
    };
}

This combines:

switch pattern
      +
record pattern
      +
automatic extraction

10. Pattern Matching vs Traditional Code

Traditional

if (obj instanceof Point) {
    Point p = (Point) obj;

    if (p.x() > 0) {
        System.out.println(p.x());
    }
}

Pattern matching

if (obj instanceof Point(int x, int y) && x > 0) {
    System.out.println(x);
}

The newer version avoids unnecessary casting and temporary variables.


Quick Revision

Feature Java Example Purpose
Regular expressions Pattern.compile(...) Match text patterns
instanceof pattern 16+ o instanceof String s Type check + variable
switch pattern 21+ case String s -> Match different types
when guard 21+ case String s when ... Add a condition
Record pattern 21+ Point(int x, int y) Extract record components

The main idea

Traditional Java:

check type
   ↓
cast
   ↓
create variable
   ↓
use variable


Pattern matching:

check type + create variable
   ↓
use variable

For example:

if (obj instanceof String s) {
    System.out.println(s.length());
}

Read this as:

"If obj is a String, give me that String as s."

And:

if (obj instanceof Point(int x, int y)) {
    System.out.println(x + y);
}

Read this as:

"If obj is a Point, give me its x and y components."