1. The problem with the classic instanceof
Let’s start with a bit of nostalgia. Before Java 16, when you needed to check an object’s type and then use it as that type, you had to write a verbose pattern with an instanceof check and an explicit cast:
Object obj = ...; // some object
if (obj instanceof String) {
String s = (String) obj;
System.out.println("String length: " + s.length());
}
Boring but common pattern
This pattern appears very often — event handlers, working with heterogeneous collections, business logic with inheritance, etc.
Why it’s inconvenient
- Code duplication — you have to specify the type twice: in instanceof and in the cast.
- Risk of error — it’s easy to accidentally cast to the wrong type and get a ClassCastException.
- “Noisy” code — worsens readability, especially with multiple checks.
2. Pattern Matching for instanceof: new syntax
Java 16 added pattern matching for instanceof. Now you check the type and immediately declare a variable of the desired type, available inside the if branch.
New syntax
if (obj instanceof String s) {
// s is already a String!
System.out.println("String length: " + s.length());
}
- After the type in instanceof, write the new variable name: String s.
- The variable is available only within the block where the condition is true.
- No need to write the cast (String) — the compiler guarantees it.
What’s the magic? The compiler checks the type and, if it matches, “unwraps” a variable of the required type. Otherwise, the block simply doesn’t execute.
What it looks like in real code
Object obj = "Hello, Java 16+!";
if (obj instanceof String s) {
System.out.println("Uppercase: " + s.toUpperCase());
} else {
System.out.println("Not a string!");
}
3. Safety and readability: why this is great
Eliminates ClassCastException
Object obj = 123;
// String s = (String) obj; // Boom! Exception.
With pattern matching, the variable is created only upon a successful type check — there’s simply no “wrong” cast.
Code gets shorter and clearer
Two lines turn into one. Less “noisy” code — easier to read and maintain.
Example: handling multiple types
public static void printInfo(Object obj) {
if (obj instanceof String s) {
System.out.println("String length " + s.length());
} else if (obj instanceof Integer i) {
System.out.println("Integer: " + (i + 1));
} else {
System.out.println("Unknown type: " + obj);
}
}
4. Usage examples and nuances
Checking multiple types
Object value = ...;
if (value instanceof String s) {
System.out.println("It's a string: " + s);
} else if (value instanceof Number n) {
System.out.println("It's a number: " + n);
} else {
System.out.println("Something else: " + value);
}
Using with null
If the object is null, instanceof always returns false — no NPE, but the block won’t run.
Object obj = null;
if (obj instanceof String s) {
// This block will NEVER run if obj == null
System.out.println("String: " + s);
} else {
System.out.println("obj is null or not a string");
}
Pattern matching with inheritance
class Animal {}
class Dog extends Animal {
void bark() { System.out.println("Woof!"); }
}
Animal a = new Dog();
if (a instanceof Dog d) {
d.bark(); // We can call Dog methods without casting!
}
Reminder: the order of checks matters — go from more specific to more general, otherwise the “narrow” checks may not trigger.
Comparison: old vs. new approach
| Old way | New way (pattern matching) |
|---|---|
|
|
5. Limitations of pattern matching for instanceof
- Variable scope. The variable from the pattern is visible only in the branch where the check is true.
if (obj instanceof String s) {
System.out.println(s); // s is accessible
}
// System.out.println(s); // Error! s is not visible here
- You can’t declare multiple variables of different types in a single condition.
// Compilation error:
if (obj instanceof String s || obj instanceof Integer i) {
// ...
}
- Requires Java 16+. On older JDK versions, the new syntax is not supported.
6. Practical examples (based on a simple app)
Let’s imagine a simple Task Manager with different task types.
Classes
class Task {
String title;
public Task(String title) { this.title = title; }
}
class BugTask extends Task {
int severity;
public BugTask(String title, int severity) {
super(title);
this.severity = severity;
}
}
class FeatureTask extends Task {
String feature;
public FeatureTask(String title, String feature) {
super(title);
this.feature = feature;
}
}
Processing with pattern matching
public static void processTask(Task t) {
if (t instanceof BugTask bug) {
System.out.println("Bug: " + bug.title + ", severity: " + bug.severity);
} else if (t instanceof FeatureTask feature) {
System.out.println("Feature: " + feature.title + ", module: " + feature.feature);
} else if (t instanceof Task task) {
System.out.println("Regular task: " + task.title);
}
}
7. Useful nuances
Table: Pattern Matching for instanceof — pros and cons
| Pros | Cons/limitations |
|---|---|
| Less code, better readability | Requires Java 16+ |
| No risk of ClassCastException | The variable is visible only inside the if block |
| Type safety at compile time | Does not work for multiple types in a single condition |
| Convenient for working with inheritance | May not be supported in older IDEs and builds |
| Suitable for any classes |
Visual diagram: how pattern matching for instanceof works
+---------------------------+
| Object obj |
+---------------------------+
|
v
if (obj instanceof Type t)
|
Yes No
(true) (false)
| |
v v
t is available t does not exist
(code) (no access)
8. Common mistakes when using pattern matching for instanceof
Mistake #1: Trying to use the variable outside the if block. You declared the variable in the pattern, exited the block — the variable no longer exists. The compiler rightly complains: “What is s?”
Mistake #2: Expecting instanceof to work with null. If the object is null, the instanceof condition is always false, the variable isn’t created. Handle null separately if needed.
Mistake #3: Using the new syntax on an older JDK/IDE. On Java 11 and below, pattern matching for instanceof isn’t available — you’ll get a syntax error. Check your JDK version.
Mistake #4: Confusion about scope. The variable from the pattern is available only in the branch where the check is true. Outside of it — it doesn’t exist.
Mistake #5: Expecting multiple types at once. You can’t declare two variables of different types in one condition: if (obj instanceof String s || obj instanceof Integer i) — not allowed. Each type needs its own check.
GO TO FULL VERSION