Pattern Matching for instanceof (Java 16+)
Pattern matching for instanceof, standardized in Java 16 (JEP 394), eliminates the redundant explicit cast that always followed an instanceof check. A single expression both tests the type AND binds the result to a typed variable, making the code shorter, safer, and less error-prone.
Contents
Before and After
Before Java 16, every instanceofinstanceofif (obj instanceof String s)sStrings
// BEFORE Java 16 — test + cast (redundant, error-prone)
if (obj instanceof String) {
String s = (String) obj; // cast is redundant — we just checked
System.out.println(s.toUpperCase());
}
// AFTER Java 16 — pattern variable bound in one step
if (obj instanceof String s) {
System.out.println(s.toUpperCase()); // s is a String here
}
// Works for any type
Object value = getValue();
if (value instanceof List<?> list) {
System.out.println("List with " + list.size() + " elements");
} else if (value instanceof Map<?,?> map) {
System.out.println("Map with " + map.size() + " entries");
} else if (value instanceof Number n) {
System.out.println("Number: " + n.doubleValue());
}
finalScope of the Binding Variable
The binding variable is only in scope where the compiler can prove the instanceof
Object obj = "Hello, World!";
// 's' is in scope inside the if-block (where instanceof is true)
if (obj instanceof String s) {
System.out.println(s.length()); // OK — s is definitely a String here
}
// System.out.println(s.length()); // COMPILE ERROR — s out of scope
// 's' is also in scope in the condition itself after &&
if (obj instanceof String s && s.length() > 5) {
System.out.println("Long string: " + s);
}
// The following is a compile error — s is not proven bound here
// if (obj instanceof String s || s.isEmpty()) { } // ERROR
Negation and Flow Scoping
When you negate the instanceof test with !
// Early return pattern — binding variable in scope after guard
static void process(Object obj) {
if (!(obj instanceof String s)) {
throw new IllegalArgumentException("Expected String, got: " + obj.getClass());
}
// s is in scope here — the instanceof is guaranteed true
System.out.println("Processing: " + s.toUpperCase());
System.out.println("Length: " + s.length());
}
// Same idea with else-branch
static String describe(Object obj) {
if (!(obj instanceof Number n)) {
return "not a number";
}
// n is in scope from here on
return "number: " + n.doubleValue();
}
Implementing equals() with Pattern Matching
One of the most common uses of instanceofequals()
// Before — typical equals() boilerplate
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Point)) return false;
Point other = (Point) obj;
return this.x == other.x && this.y == other.y;
}
// After — cleaner with pattern matching
@Override
public boolean equals(Object obj) {
return this == obj ||
obj instanceof Point p &&
this.x == p.x &&
this.y == p.y;
}
// For more complex classes
public class Order {
private final String id;
private final double total;
private final String customerId;
@Override
public boolean equals(Object obj) {
return obj instanceof Order o &&
Objects.equals(this.id, o.id) &&
Double.compare(this.total, o.total) == 0 &&
Objects.equals(this.customerId, o.customerId);
}
}
recordequals()Combining with && and ||
The binding variable introduced by a pattern can be used in the same &&if (obj instanceof String s && s.length() > 5)s||sinstanceofinstanceof
// && — binding variable available in right operand if test passed on left
if (obj instanceof String s && !s.isBlank() && s.startsWith("http")) {
System.out.println("URL: " + s);
}
// Practical: null-safe type check
Object maybeNull = getResult(); // could be null
if (maybeNull instanceof String s) {
// null never matches instanceof — this is null-safe
System.out.println("Got string: " + s);
}
// instanceof returns false for null — no NullPointerException
Object nullRef = null;
System.out.println(nullRef instanceof String); // false (no NPE)
// Combining multiple patterns in a chain (pre-switch-expression style)
static double toDouble(Object value) {
if (value instanceof Double d) return d;
if (value instanceof Float f) return f.doubleValue();
if (value instanceof Long l) return l.doubleValue();
if (value instanceof Integer i) return i.doubleValue();
if (value instanceof String s) return Double.parseDouble(s);
throw new IllegalArgumentException("Cannot convert to double: " + value);
}
instanceofswitch