Pattern Matching

This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Pattern matching tests whether a value has a certain shape and, in the same step, binds its parts to variables. Java applies it to instanceof and to switch. This page follows dev.java "Pattern Matching" and the Java SE 25 pattern-matching language guide.

Pattern Matching for instanceof

A type pattern instanceof Type name tests the type and, when it matches, binds the value to name already cast to Type, collapsing the test-cast-assign trio into one. It was finalised in Java 16 by JEP 394; see Pattern Matching for the instanceof Operator.

Object value = "hello";

// before pattern matching
if (value instanceof String) {
    String s = (String) value;
    System.out.println(s.length());
}

// with pattern matching
if (value instanceof String s) {
    System.out.println(s.length());     // s is in scope, already typed as String
}

The binding variable’s scope follows flow scoping: it is in scope exactly where the compiler can prove the pattern matched. That includes the rest of an && chain and the code after an early-exit if.

static String describe(Object o) {
    if (o instanceof String s && s.length() > 3) {   // s is usable in the right operand of &&
        return "long string: " + s;
    }
    if (!(o instanceof Integer i)) {
        return "not an int";                          // i is NOT in scope on this path
    }
    return "int squared: " + (i * i);                 // i IS in scope: reached only if it matched
}

Flow scoping also means || does not extend scope — o instanceof String s || s.isEmpty() does not compile — and neither does the code after a non-matching branch that has no early exit.

Type Patterns in switch

switch accepts type patterns in case labels, turning a chain of if / else if on instanceof into a single construct. Finalised in Java 21 by JEP 441; see Pattern Matching for switch.

sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}

static double area(Shape shape) {
    return switch (shape) {
        case Circle c    -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t  -> 0.5 * t.base() * t.height();
    };
}

null handling

A traditional switch throws NullPointerException on a null selector. A pattern switch still does — unless it has an explicit case null:

static String label(Object o) {
    return switch (o) {
        case null      -> "nothing";
        case String s  -> "text: " + s;
        case Integer i -> "number: " + i;
        default        -> "other";
    };
}

case null may stand alone or be combined with the default as case null, default -> ....

Exhaustiveness and the total pattern

A pattern switch used as an expression, and a pattern switch statement, must be exhaustive — it must cover every possible value — or it does not compile. Over a sealed hierarchy the compiler knows the complete list of permitted subtypes, so listing each one is enough and no default is needed:

static String kind(Shape shape) {
    return switch (shape) {
        case Circle c    -> "round";
        case Rectangle r -> "boxy";
        case Triangle t  -> "pointy";
        // no default: the three permitted subtypes are exhaustive
    };
}

If Shape later gains a fourth permits entry, this switch stops compiling until the new case is handled — a compile-time reminder that a default branch would have silently swallowed. A total (unconditional) pattern such as case Object o or case var x covers everything by itself and makes the switch exhaustive on its own.

Record Deconstruction Patterns

A record pattern matches a record and binds its components in one step, following the canonical component order. Finalised in Java 21 by JEP 440; see Record Patterns.

record Point(int x, int y) {}
record Line(Point from, Point to) {}

static String render(Object o) {
    return switch (o) {
        case Point(int x, int y) -> "point at " + x + "," + y;
        case Line(Point(var x1, var y1), Point(var x2, var y2)) ->     // nested patterns
                "line " + x1 + "," + y1 + " -> " + x2 + "," + y2;
        default -> "?";
    };
}

A sub-pattern may name the component type explicitly (int x) or use var to infer it (var x). Nesting a record pattern inside another, as in Line(Point(…​), Point(…​)), deconstructs the whole tree at once; if a nested component fails to match — a null where a nested record pattern is expected — the outer pattern simply fails rather than throwing. Record patterns also work with instanceof:

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

Guarded Patterns with when

A when clause attaches a boolean test to a case label. The label matches only if the pattern matches and the guard is true; otherwise evaluation continues with the following labels.

static String classify(Shape shape) {
    return switch (shape) {
        case Circle c when c.radius() == 0            -> "degenerate circle";
        case Circle c                                 -> "circle r=" + c.radius();
        case Rectangle r when r.width() == r.height() -> "square";
        case Rectangle r                             -> "rectangle";
        case Triangle t                              -> "triangle";
    };
}

Order matters: place the guarded, more specific case before the unguarded case for the same type, or the unguarded label matches first. Because a guard can always evaluate to false, a guarded case never counts toward exhaustiveness — the example still needs the plain case Circle c and case Rectangle r to compile. Patterns and the when clause are specified in JLS 14.30.

See Also

  • Records and Sealed Classes — the record and sealed types that patterns deconstruct and exhaust.

  • Control Flow — switch expressions and statements in general, and the arrow form.

  • Enums — constant-label switch, the older exhaustiveness story.

  • Interfaces — sealed interface and its permitted implementations.