Home/Learn/Java A–Z/Pattern Matching

Pattern Matching

Intermediate
Modern Java

Pattern matching eliminates verbose casting and enables expressive type-safe code with instanceof and switch patterns.

Overview

Pattern matching, progressively introduced from Java 14 through Java 21, replaces boilerplate instanceof checks and casts. Type patterns in instanceof let you declare a binding variable inline. Pattern matching for switch (Java 21) extends this to switch expressions, supporting type patterns, guarded patterns, and null handling — making exhaustive, expressive dispatch over type hierarchies straightforward.

Pattern Matching for instanceof

Before pattern matching, instanceof required a separate cast. With type patterns, the cast and variable declaration are combined into one.

The binding variable is in scope only where the pattern is guaranteed to match. The compiler enforces this through flow typing.

PatternInstanceof.java
// Old style
Object obj = "Hello, Java!";
if (obj instanceof String) {
    String s = (String) obj;   // redundant cast
    System.out.println(s.length());
}

// Pattern matching (Java 16+)
if (obj instanceof String s) {
    System.out.println(s.length()); // s is already a String
}

// Combining with conditions
if (obj instanceof String s && s.length() > 5) {
    System.out.println("Long string: " + s);
}

Pattern Matching in Switch (Java 21)

Switch expressions can match on types, apply guards (when clauses), and handle null explicitly. This replaces chains of if-instanceof-else with concise, readable dispatch.

Guarded patterns use when to add a boolean condition to a type pattern case.

SwitchPattern.java
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double r) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
record Triangle(double b, double ht) implements Shape {}

double area(Shape shape) {
    return switch (shape) {
        case Circle c             -> Math.PI * c.r() * c.r();
        case Rectangle r
            when r.w() == r.h()  -> r.w() * r.w(); // square
        case Rectangle r          -> r.w() * r.h();
        case Triangle t           -> 0.5 * t.b() * t.ht();
    };
}

Null Handling in Switch

Before Java 21, switching on null threw a NullPointerException. Now you can add a case null to handle it explicitly, or combine case null, default for a catch-all.

This makes null-safe dispatch concise without wrapping in Optional or pre-checking.

NullSwitch.java
String describe(Object obj) {
    return switch (obj) {
        case null             -> "null value";
        case Integer i        -> "int: " + i;
        case String s
            when s.isEmpty()  -> "empty string";
        case String s         -> "string: " + s;
        default               -> "other: " + obj.getClass().getSimpleName();
    };
}

Key Points to Remember

  • Type pattern instanceof String s combines check and cast into one.
  • Pattern binding variables are flow-scoped — available only where the match holds.
  • Switch pattern matching (Java 21) supports type patterns, guarded patterns (when), and null.
  • Sealed types + switch patterns = exhaustive dispatch without default.
  • Guards use when keyword: case String s when s.length() > 10.

Practice Pattern Matching in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What is flow typing in the context of Java pattern matching?

MediumGoogle
2

How does a guarded pattern differ from a regular type pattern?

MediumAmazon
3

Why did Java historically throw NPE when switching on null, and how is it handled in Java 21?

MediumOracle
4

When is a switch expression with type patterns considered exhaustive?

HardMicrosoft
5

Explain the order of evaluation for pattern cases in a switch.

HardUber

Ask Aria about Pattern Matching

Your personal AI tutor — ask anything about this concept