Java Instanceof and Pattern Matching
A payments screen receives a list of Payment objects. A UPI payment should show its UPI id, and a card payment the last four digits of the card. Through a Payment variable you can't see either. You first have to ask, "is this object a UPI payment?", and then treat it as one. The instanceof operator asks that question, and since Java 16, pattern matching for instanceof asks it and gives you a correctly typed variable in one step.
The Classic Way
if (p instanceof UpiPayment) {
UpiPayment upi = (UpiPayment) p; // test, then cast
System.out.println(upi.upiId);
}x instanceof Type is true when x refers to an object of Type or any subtype of it. It's false when x is null, so it never throws an exception. The cast on the next line repeats the type name, and if someone later changes one of the two types but not the other, the code breaks.
Pattern Matching
public class Payments {
static class Payment {
long amount;
Payment(long amount) { this.amount = amount; }
}
static class UpiPayment extends Payment {
String upiId;
UpiPayment(long amount, String upiId) {
super(amount);
this.upiId = upiId;
}
}
static class CardPayment extends Payment {
String last4;
CardPayment(long amount, String last4) {
super(amount);
this.last4 = last4;
}
}
static String describe(Payment p) {
if (p instanceof UpiPayment upi) {
return "UPI " + upi.upiId + ": Rs. " + upi.amount;
} else if (p instanceof CardPayment card && card.amount > 10000) {
return "Large card payment, card ending " + card.last4;
} else if (p instanceof CardPayment card) {
return "Card ending " + card.last4 + ": Rs. " + card.amount;
}
return "Unknown payment";
}
public static void main(String[] args) {
System.out.println(describe(new UpiPayment(1200, "navika@upi")));
System.out.println(describe(new CardPayment(25000, "4321")));
System.out.println(describe(new CardPayment(800, "9876")));
System.out.println(describe(null));
}
}UPI navika@upi: Rs. 1200
Large card payment, card ending 4321
Card ending 9876: Rs. 800
Unknown paymentHow it works
p instanceof UpiPayment upitests the type and, if the test passes, declaresupialready cast toUpiPayment.upiis called a binding variable (or pattern variable).p instanceof CardPayment card && card.amount > 10000: after&&, the test has passed, socardcan be used in the same condition.describe(null): everyinstanceoftest isfalsefornull, so the method falls through to"Unknown payment"without aNullPointerException.
Try it yourself: add a CashPayment class and a branch for it. What does describe print for a cash payment before you add the branch?
Where the Binding Variable Exists
The compiler tracks where the test is definitely true, and makes the variable available only there. This is called flow scoping:
| Code | Is the variable usable? |
|---|---|
if (o instanceof String s) { s.length(); } | Yes, inside the if block |
if (o instanceof String s && s.length() > 3) | Yes, after && |
if (o instanceof String s || s.isEmpty()) | No: after ||, the test may have failed |
if (!(o instanceof String s)) return; s.length(); | Yes, after the if, because the method returned otherwise |
The || case is a compile error:
Inst2.java:9: error: cannot find symbol
if (o instanceof String u || u.isEmpty()) {}
^
symbol: variable u
location: class Inst2
1 errorThe negated form, if (!(o instanceof String s)) return;, is handy at the start of a method: it rejects the wrong type early and leaves s usable for the rest of the method.
Impossible Tests Don't Compile
If the compiler can prove a test could never be true, it rejects it:
Inst.java:4: error: incompatible types: Integer cannot be converted to String
System.out.println(n instanceof String);
^
1 errorAn Integer variable can never refer to a String, because neither class extends the other.
Common Mistakes
Testing, then casting by hand. Use the pattern form; it's shorter and the type is written once.
Using the binding variable after || or outside the true branch. It's only in scope where the test is definitely true.
Checking obj != null && obj instanceof X. The null check is redundant: instanceof already returns false for null.
Replacing polymorphism with instanceof chains. Each new type then means editing every chain.
Interview Corner
What does instanceof return for null?
Always false. null isn't an instance of any type, so x instanceof T is false when x is null, and it never throws a NullPointerException.
What is pattern matching for instanceof?
Since Java 16, obj instanceof Type name tests the type and, when it matches, binds a new variable of that type, removing the separate cast. The binding variable uses flow scoping: it's available only where the compiler knows the test succeeded, such as inside the if block, after &&, or after an early return in the negated case.
Practice
- Easy: Write
static String kind(Object o)that returns"text of length N"for aString,"number N"for anInteger, and"something else"otherwise. - Harder: Rewrite
describefromPayments.javaso thePaymentclass has an overridabledescribe()method and theinstanceofchecks disappear. Which version is easier to extend?
Key Takeaways
Was this tutorial helpful?