5% of payment transactions skipped silently because null passes instanceof returns false.
Basic instanceof Check
instanceof evaluates at runtime using the JVM's type system. It returns true if the object's actual class is the specified type or any subtype of it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package io.thecodeforge.java.operators;
public class InstanceofBasics {
public static void main(String[] args) {
Object text = "Hello, Forge";
Object number = 42;
Object nothing = null;
System.out.println(text instanceof String); // true
System.out.println(number instanceof String); // false
System.out.println(nothing instanceof String); // false — null is never instanceof anything
// Subtype check
Number n = 3.14;
System.out.println(n instanceof Number); // true
System.out.println(n instanceof Double); // true — Double extends Number
System.out.println(n instanceof Integer);// false
}
}Output
true
false
false
true
true
false
Production Insight
A common production bug: developers forget that null returns false.
Code that branched on instanceof(String) never entered the block, silently skipping processing.
Rule: always treat null as a separate case — instanceof is not a null check.
Key Takeaway
instanceof returns true for the type and all supertypes.
null always produces false.
Use pattern matching to avoid manual casts.
Pattern Matching instanceof — Java 16+
Before Java 16, you had to cast explicitly after an instanceof check — verbose and error-prone. Pattern matching collapses the check and cast into one expression.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package io.thecodeforge.java.operators;
public class PatternMatching {
// Old style — before Java 16
static String describeOld(Object obj) {
if (obj instanceof String) {
String s = (String) obj; // manual cast
return "String of length " + s.length();
} else if (obj instanceof Integer) {
Integer i = (Integer) obj;
return "Integer: " + (i > 0 ? "positive" : "non-positive");
}
return "Unknown: " + obj.getClass().getSimpleName();
}
// Pattern matching — Java 16+ (binding variable declared inline)
static String describe(Object obj) {
if (obj instanceof String s) {
return "String of length " + s.length(); // s is in scope here
} else if (obj instanceof Integer i && i > 0) {
return "Positive integer: " + i; // can use binding var in condition
} else if (obj instanceof Integer i) {
return "Non-positive integer: " + i;
}
return "Unknown: " + obj.getClass().getSimpleName();
}
public static void main(String[] args) {
System.out.println(describe("TheCodeForge")); // String of length 12
System.out.println(describe(42)); // Positive integer: 42
System.out.println(describe(-5)); // Non-positive integer: -5
System.out.println(describe(3.14)); // Unknown: Double
}
}Output
String of length 12
Positive integer: 42
Non-positive integer: -5
Unknown: Double
Production Insight
Pattern matching variables are scoped to the if-block — not available after it.
A common mistake: trying to use the binding variable outside the if, causing compile error.
Rule: treat the binding variable as locally scoped; extract if you need it later.
Key Takeaway
Java 16+ reduces boilerplate: one expression instead of two.
The binding variable is final — you can't reassign it.
Use && with conditions when the binding is needed in the condition.
instanceof with Interfaces
instanceof works with interfaces too. An object passes the instanceof check if its class implements the interface, directly or through a superclass.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package io.thecodeforge.java.operators;
import java.util.ArrayList;
import java.util.List;
public class InterfaceCheck {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
System.out.println(list instanceof List); // true
System.out.println(list instanceof ArrayList); // true
System.out.println(list instanceof Iterable); // true — List extends Iterable
// Useful pattern: safe processing
Object[] items = {"hello", 42, null, new ArrayList<>()};
for (Object item : items) {
if (item instanceof Iterable<?> it) {
System.out.println("Iterable found: " + it.getClass().getSimpleName());
} else if (item instanceof String s) {
System.out.println("String: " + s.toUpperCase());
} else {
System.out.println("Other: " + item);
}
}
}
}Output
true
true
true
String: HELLO
Other: 42
Other: null
Iterable found: ArrayList
Production Insight
If a class implements an interface through inheritance, instanceof still works.
But beware of proxy objects (like Hibernate or Spring AOP proxies) — they may fail instanceof for the original class.
Rule: when using reflective proxies, check the proxy interfaces instead.
Key Takeaway
instanceof works with interfaces — it's all about runtime type.
Java collections interfaces chain: List -> Collection -> Iterable.
Proxy objects can break instanceof; check for UnsupportedOperationException.
instanceof with Sealed Classes
Sealed classes (Java 17) restrict which classes can extend them. instanceof combined with pattern matching becomes more powerful because the compiler knows all permitted subtypes. This enables exhaustive checks without default branches.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package io.thecodeforge.java.operators;
sealed interface Shape permits Circle, Rectangle {}
final class Circle implements Shape { double radius; Circle(double r) { radius = r; } }
final class Rectangle implements Shape { double w, h; Rectangle(double w, double h) { this.w=w; this.h=h; } }
public class SealedInstanceof {
static double area(Shape s) {
if (s instanceof Circle c) {
return Math.PI * c.radius * c.radius;
} else if (s instanceof Rectangle r) {
return r.w * r.h;
}
// No else needed — compiler knows all cases covered
// But it's good practice to keep for future extensibility
throw new IllegalArgumentException("Unknown shape");
}
public static void main(String[] args) {
System.out.println(area(new Circle(5))); // 78.53981633974483
System.out.println(area(new Rectangle(3,4))); // 12.0
}
}Output
78.53981633974483
12.0
Production Insight
Sealed classes give compile-time exhaustiveness: you can't forget a subtype.
But if you later add a new permitted subtype, the compiler will flag missing cases.
Rule: use sealed classes to make instanceof-based dispatch safe and maintainable.
Key Takeaway
Sealed classes + pattern matching = compiler-verified type switches.
No default branch needed — the compiler knows all subtypes.
Adding a new subtype forces updates in all instanceof chains — a feature, not a bug.
instanceof in Switch Expressions (Java 17+)
Java 17 extended pattern matching to switch expressions and statements. Instead of chained if-else instanceof blocks, you can use a switch with type patterns. This is especially clean with sealed classes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.thecodeforge.java.operators;
sealed interface Animal permits Dog, Cat, Bird {}
record Dog(String name) implements Animal {}
record Cat(String name) implements Animal {}
record Bird(String name, double wingspan) implements Animal {}
public class SwitchPatternMatching {
static String describe(Animal a) {
return switch (a) {
case Dog(var name) -> "Dog named " + name;
case Cat(var name) -> "Cat named " + name;
case Bird(var name, var ws) -> "Bird with wingspan " + ws;
};
}
public static void main(String[] args) {
System.out.println(describe(new Dog("Rex"))); // Dog named Rex
System.out.println(describe(new Cat("Luna"))); // Cat named Luna
System.out.println(describe(new Bird("Tweety", 0.3))); // Bird with wingspan 0.3
}
}Output
Dog named Rex
Cat named Luna
Bird with wingspan 0.3
Production Insight
Switch pattern matching with sealed classes eliminates the need for default branches.
But if the sealed hierarchy has a non-sealed subtype, the compiler won't enforce exhaustiveness.
Rule: always mark all permitted subtypes as final, sealed, or non-sealed explicitly.
Key Takeaway
Switch on type patterns is cleaner than chained if-else instanceof.
Exhaustiveness is enforced when switching on sealed types.
Use record patterns to destructure values inside the case.