A pattern tests whether a value has a certain shape and, if it does, takes it apart
into new variables. Patterns are used after instanceof (Java 16) and as
case labels (Java 21). They nest, so one pattern can test and destructure a
whole tree of records (JLS 14.30).
Grammar
Pattern:
TypePattern
RecordPattern
TypePattern:
LocalVariableDeclaration (one variable, no initialiser)
RecordPattern:
ReferenceType ( [ComponentPattern {, ComponentPattern}] )
ComponentPattern:
Pattern
MatchAllPattern
MatchAllPattern:
_
| Pattern | Example | Matches | Since |
|---|---|---|---|
| Type pattern | String s | A value that is a String; binds it to s | 16 |
| Record pattern | Point(int x, int y) | A Point whose components match the nested patterns | 21 |
Type pattern with var | Point(var x, var y) | Any value of the component’s type (only inside a record pattern) | 21 |
| Unnamed type pattern | String _ | Like a type pattern, but binds nothing | 22 |
| Match-all pattern | Point(_, var y) | Any component, including null (only inside a record pattern) | 22 |
instanceof with a pattern
// Before Java 16
if (obj instanceof String) {
String s = (String) obj;
use(s);
}
// Java 16 and later
if (obj instanceof String s) {
use(s);
}
if (obj instanceof String s && s.length() > 3) { … } // s is in scope after &&
if (!(obj instanceof String s)) return; // s is in scope after the if
use(s);
A pattern variable is an ordinary local variable: it may be assigned, unless it is
declared final, as in obj instanceof final String s.
instanceof with a pattern is false for null, so the
variable is never null.
Flow scoping
A pattern variable is in scope exactly where the compiler can be sure the pattern matched (JLS 6.3.1). The rules follow the boolean operators:
| Expression or statement | The variable of x instanceof T v is in scope |
|---|---|
a && b | In b, and after it if the whole is true |
a || b | In b if a introduces it when false, as in !(x instanceof String s) || s.isEmpty(); after it if the whole is false |
!a | Swaps the true and false sets |
c ? a : b | In a when c introduces it when true; in b when false |
if (e) S1 else S2 | In S1 when e is true, in S2 when false |
if (!e) S where S cannot complete normally | In the statements after the if |
while (e) S | In S; after the loop when e is false and there is no break |
A case label | In its guard and in its rule or statement group |
A pattern variable may not have the same name as a local variable already in scope, and two patterns that could both be in scope at the same point cannot declare the same name.
Record patterns
A record pattern (Java 21, JEP 440) names a record class and gives one pattern for each component. It matches a non-null value of that record type whose components, read through the accessor methods, match the nested patterns.
record Point(int x, int y) {}
enum Colour { RED, GREEN }
record ColouredPoint(Point p, Colour c) {}
record Rectangle(ColouredPoint topLeft, ColouredPoint bottomRight) {}
static int width(Object o) {
if (o instanceof Rectangle(ColouredPoint(Point(var x1, _), _),
ColouredPoint(Point(var x2, _), _))) {
return Math.abs(x2 - x1);
}
return 0;
}
- The number of component patterns must equal the number of components.
- A record pattern never matches
null. - A generic record pattern may leave out the type arguments; they are inferred:
case Box(String s)for aBox<T>selector of typeBox<String>. - If an accessor throws, matching throws
MatchExceptionwith that exception as its cause. - Inside a record pattern, a type pattern that covers the component’s whole type
(including
varand_) also matchesnullcomponents; one that tests a narrower type does not.
Unnamed variables and patterns
Since Java 22 (JEP 456) the underscore _ can stand for a variable or a
pattern whose value is not needed. Any number of _ declarations may be in
scope at once, and none can be read.
| Where | Example |
|---|---|
| Local variable | var _ = queue.remove(); |
Enhanced for variable | for (var _ : items) n++; |
Basic for variable | for (int i = 0, _ = sideEffect(); i < n; i++) |
| Resource | try (var _ = scope.open()) { … } |
| Catch parameter | catch (NumberFormatException _) |
| Lambda parameter | map.forEach((_, v) -> total += v); |
| Type pattern | case Circle _ -> |
| Component pattern | case Point(var x, _) -> |
Fields and method parameters cannot be unnamed. From Java 9 to 21, _
could not be used at all, except as a preview feature in Java 21; in Java 8 it was an
identifier with a warning.
Patterns and null
null instanceof T vis always false.- A top-level type pattern in a
casenever matchesnull; onlycase nulldoes. Without it, a switch onnullthrowsNullPointerException. - A record pattern never matches
null; but a nested type pattern that is unconditional for its component type does match anullcomponent, as described above.
Unconditional patterns, dominance and exhaustiveness
A type pattern is unconditional for a type T if every value of
T matches it, that is, if the erasure of T is a subtype of
the pattern’s type. A record pattern is never unconditional, because of
null. These ideas drive the rules for switches:
- A
casepattern is an error if an earlier unguarded pattern dominates it, that is, matches everything it matches. - A set of patterns is exhaustive when it covers a type: an unconditional pattern, all permitted subtypes of a sealed type, all constants of an enum, or record patterns covering every combination of component cases.
sealed interface Shape permits Circle, Square {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}
record Pair(Shape a, Shape b) {}
static String kinds(Pair p) {
return switch (p) { // exhaustive without default
case Pair(Circle _, Circle _) -> "two circles";
case Pair(Circle _, Square _) -> "circle, square";
case Pair(Square _, var b) -> "square, then " + b;
};
}
The full rules for switches are on the Switch page.
Primitive patterns (preview)
Primitive types in patterns, instanceof and switch is a
preview feature in Java 23 to 27 (JEP 455, 488, 507, 530 and 532, its fifth preview in
Java 27). It is not part of the language unless compiled with --enable-preview
--release 27. It allows a primitive type pattern to test whether a value can be
converted without loss, and allows switches on boolean,
long, float and double:
// preview only: javac --enable-preview --release 27
if (i instanceof byte b) { … } // true when the int value fits in a byte
switch (flag) { // a boolean selector
case true -> start();
case false -> stop();
}