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 —
switchexpressions and statements in general, and the arrow form. -
Enums — constant-label
switch, the older exhaustiveness story. -
Interfaces —
sealed interfaceand its permitted implementations.