Pattern Matching

Pattern matching is one of the most significant features in modern Java. It allows you to check if an object has a specific structure (like a type or a record shape) and extract data from it in a single, concise operation.

1. The instanceof Pattern

For 25 years, Java developers wrote this boilerplate:

// The Old Way
if (obj instanceof String) {
    String s = (String) obj; // Explicit cast (redundant!)
    System.out.println(s.length());
}

Now, Java combines the check and the cast:

// The Modern Way (Java 16+)
if (obj instanceof String s) {
    // 's' is the "Binding Variable"
    // It is ONLY in scope if the check passes!
    System.out.println(s.length());
}

Deep Dive: Flow Scoping

The variable s is flow-scoped. It exists only where the compiler can guarantee the pattern matched.

// 1. In the same condition
if (obj instanceof String s && s.length() > 5) {
    // Valid: s exists here
}

// 2. Inverted Logic
if (!(obj instanceof String s)) {
    return; // If it's NOT a string, we leave
}
// Valid: s exists here because we survived the return!
System.out.println(s.toUpperCase());

2. Pattern Matching for Switch

Java 21 allows using patterns in switch cases. This replaces long chains of if-else-if.

Interactive Demo: Pattern Lab

Simulate the Dispatch

Object o = input; return switch (o) {   case null → "It is null";   case String s → "String: " + s;   case Integer i → "Int: " + i;   default → "Unknown type"; };
Result: ...

Performance Reality: O(1) vs O(N)

Why use switch instead of if-else?

  • If-Else Chain: The JVM must check each condition sequentially. If you have 20 types, the last one takes 20 checks. This is O(N).
  • Switch: The JVM uses indy (invokedynamic) or optimized bytecode (type switch) to jump directly to the correct label. It is roughly O(1) (constant time), regardless of how many cases you have.

3. Record Patterns (Destructuring)

This is where it gets powerful. You can match a Record and extract its fields in one go.

record Point(int x, int y) {}
record Circle(Point center, int radius) {}

Object obj = new Circle(new Point(0, 0), 10);

if (obj instanceof Circle(Point p, int r)) {
    // Extracted p and r directly
    System.out.println("Radius: " + r);
}

Nested Patterns

You can go deeper! This eliminates the “train wreck” of getCenter().getX().

if (obj instanceof Circle(Point(int x, int y), int r)) {
    // Extracted x, y, and r directly!
    System.out.println("Circle at " + x + "," + y);
}

4. Guarded Patterns (when)

Sometimes type checking isn’t enough. You need to check values too.

Before (Nested If)

case String s -> {
    if (s.length() > 5) yield "Long string";
    else yield "Short string";
}

After (Guarded Pattern)

Use the when keyword to add a boolean condition to the pattern.

return switch (obj) {
    // Specific case first!
    case String s when s.length() > 5 -> "Long string";
    // General case second
    case String s                     -> "Short string";
    case Integer i                    -> "Number";
    default                           -> "Unknown";
};

[!IMPORTANT] The compiler forces you to put specific cases before general cases. If you swap the order above, the compiler will error: “This case is dominated by a preceding case.”


5. Null Handling

In the old switch, passing null threw a NullPointerException immediately. In the modern switch, you can handle null explicitly.

switch (obj) {
    case null      -> System.out.println("It is null");
    case String s  -> System.out.println("It is a string");
    default        -> System.out.println("Something else");
}

If you don’t have a case null, and the input is null, it will still throw NPE (to preserve backward compatibility).


6. Summary

  • instanceof T var: Checks type and casts to var (flow-scoped).
  • Switch Patterns: High-performance dispatch based on type.
  • Record Patterns: Deconstruct nested data structures easily.
  • Guarded Patterns (when): Refine matches with boolean logic.
  • Best Practice: Use pattern matching to replace Visitor patterns. It makes the code more localized and easier to read.

[!TIP] Use Pattern Matching to simplify JSON parsing logic or AST (Abstract Syntax Tree) traversal. It makes code readable and declarative.