Enhanced instanceof & Record Patterns
Enhanced instanceof & Record Patterns
Before Java 16, testing whether an object is of a specific type and then using it as that type required two separate steps: an instanceof check followed by an explicit cast. Java 16 introduced pattern matching for instanceof, and Java 21 extended the same idea to record patterns — allowing you to deconstruct a record's components directly inside a pattern. Together these features eliminate a class of boilerplate that has existed in Java since its first release.
The Old Way: Test, Then Cast
Every Java developer has written code like this:
The cast on the second line is purely ceremonial. The compiler already knows the type because the instanceof check passed. The cast exists only because the language required it.
Pattern Matching for instanceof
With pattern matching you combine the test and the binding into a single expression:
The pattern variable c is in scope only where the pattern is guaranteed to have matched. The compiler enforces this: you cannot accidentally use c in a branch where the test failed.
if, not in the else branch and never outside the statement. The compiler rejects any attempt to use it where the match is not guaranteed.
Using the Pattern Variable in Conditions
Pattern variables can appear in the same boolean expression, enabling compact guards:
Because Java short-circuits &&, the right-hand side is only reached when the pattern matched, so s is guaranteed to be a String there. The same logic means || would not be safe — and the compiler rejects it.
Pattern Matching in Switch Expressions (Java 21)
Java 21 generalised patterns to switch. You can match on type, bind a variable, and add a when guard in a single arm:
Because Shape is sealed, the compiler knows the three permitted subtypes are exhaustive and does not require a default arm. Adding a fourth subtype later turns the missing arm into a compile error — not a silent runtime failure.
When Guards
A when clause refines a pattern arm with an arbitrary boolean expression:
Arms are evaluated top-to-bottom. The first arm whose type pattern matches and whose when guard is true wins. This makes the ordering meaningful — unlike traditional switch on primitives, falling through is not an issue.
Record Patterns: Deconstruction in Place
Java 21 introduced record patterns, which let you match a record type and bind its components simultaneously. Instead of calling accessors after the match, you name the components directly in the pattern:
The record pattern Line(Point start, Point end) both tests that obj is a Line and deconstructs it into its two components in a single operation.
Nested Record Patterns
Record patterns compose. You can nest them to reach deeply into a data structure without intermediate variables:
The inner pattern Point(int x, int y) deconstructs the start component. The outer pattern deconstructs the Line. Both bindings are in scope in the body.
null. If obj is null, the entire pattern fails immediately — no NullPointerException is thrown. This is consistent with all pattern matching in Java.
Record Patterns in Switch
Record patterns shine most in switch where multiple shapes need to be deconstructed:
This is a complete, recursive expression evaluator in seven lines. The record patterns extract l and r from the Add and Mul nodes on the same line that identifies the node type — no intermediate variables, no accessors.
Trade-offs and When to Use These Features
- Use pattern matching for
instanceofwhenever you would previously have written a test followed by a cast. It is strictly better: less noise, no hiddenClassCastExceptionrisk from a mistyped variable name. - Prefer sealed hierarchies + switch patterns over long
if/else instanceofchains. The exhaustiveness check makes the code more robust and easier to maintain. - Use record patterns when the structure of the data is the point. They make algorithmic code that walks recursive data (expression trees, ASTs, JSON models) dramatically cleaner.
- Do not over-nest. Deeply nested record patterns (
A(B(C(D d)))) can be hard to read. If the structure is more than two levels deep, extracting an intermediate variable is often clearer.
Summary
Pattern matching for instanceof (Java 16) replaces the test-then-cast idiom with a single binding expression. Switch patterns (Java 21) extend this to multi-arm dispatching with when guards and sealed-type exhaustiveness checks. Record patterns (Java 21) add deconstruction — binding a record's components directly in the pattern. These three features together make type-driven code in Java concise, safe, and compiler-verified, bringing the language in line with the pattern-matching capabilities found in Haskell, Scala, and Kotlin.