Java
Instanceof
Generics
Programming
Object-Oriented

Java Instanceof and Generics

Object-Oriented Design practice on Codemia

Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.

Practice OOD

Introduction

instanceof and generics are both about types, but they operate at different moments. Generics mostly help at compile time, while instanceof checks the runtime type of an object you already have.

The confusing part is that Java generics use type erasure. That means the generic type argument usually is not available at runtime, which is exactly why instanceof List<String> is not allowed.

What instanceof Actually Checks

instanceof answers a runtime question: is this object an instance of this class or interface?

java
1Object value = "hello";
2
3if (value instanceof String) {
4    System.out.println("It is a String");
5}

If value is null, the result is false. If the object implements an interface or extends a class being tested, the result is true.

Modern Java also allows pattern matching with instanceof, which combines the check and the cast:

java
1Object value = "hello";
2
3if (value instanceof String text) {
4    System.out.println(text.toUpperCase());
5}

That is cleaner than checking and then casting separately.

What Generics Give You

Generics improve compile-time type safety:

java
1import java.util.ArrayList;
2import java.util.List;
3
4List<String> names = new ArrayList<>();
5names.add("Ada");
6names.add("Linus");
7
8String first = names.get(0);
9System.out.println(first);

Because the list is declared as List<String>, the compiler prevents you from adding unrelated types and removes the need for casts when reading.

Why instanceof List<String> Does Not Compile

This fails:

java
1import java.util.ArrayList;
2import java.util.List;
3
4List<String> names = new ArrayList<>();
5
6if (names instanceof List<String>) {
7    System.out.println("Never compiles");
8}

The reason is type erasure. At runtime, Java generally knows that the object is some kind of List, but not that it is specifically a List<String> or List<Integer>.

After compilation, both become effectively just List for runtime type checking.

What You Can Check Instead

You can check against the raw generic type, or better, against a wildcard form:

java
1import java.util.ArrayList;
2import java.util.List;
3
4Object value = new ArrayList<String>();
5
6if (value instanceof List<?> list) {
7    System.out.println("List size = " + list.size());
8}

This is legal because List<?> means "a list of some unknown type," which does not require runtime knowledge of the erased type argument.

What you cannot do is conclude from that check that the list elements are String values.

If You Need to Validate Element Types

Sometimes the real question is not "is this a list," but "is this a list whose elements are strings." Since the generic argument is erased, you must inspect the contents manually:

java
1import java.util.List;
2
3static boolean isListOfStrings(Object value) {
4    if (!(value instanceof List<?> list)) {
5        return false;
6    }
7
8    for (Object item : list) {
9        if (!(item instanceof String)) {
10            return false;
11        }
12    }
13    return true;
14}
15
16public static void main(String[] args) {
17    System.out.println(isListOfStrings(List.of("a", "b"))); // true
18    System.out.println(isListOfStrings(List.of("a", 1)));   // false
19}

That is the honest runtime check. It is about elements, not generic metadata.

Wildcards and API Design

Generics become more flexible with wildcards:

java
1import java.util.List;
2
3static double sum(List<? extends Number> numbers) {
4    double total = 0;
5    for (Number n : numbers) {
6        total += n.doubleValue();
7    }
8    return total;
9}

This method accepts List<Integer>, List<Double>, and other numeric lists because it reads values as Number. That is a generic design concern, not an instanceof concern, but the two ideas often appear together in the same code.

Common Pitfalls

The biggest pitfall is expecting generics to exist at runtime in the same way they exist at compile time. In ordinary Java code, they usually do not because of type erasure.

Another mistake is using raw types such as List everywhere just to get instanceof to compile. That throws away compile-time safety. Prefer List<?> when you mean "a list of unknown element type."

Developers also overuse instanceof when a better design would use polymorphism, interfaces, or generic method signatures. A runtime type check is sometimes correct, but it should not replace good type design.

Finally, a successful instanceof List<?> check says nothing about the element type. If element type matters, validate the contents.

Summary

  • 'instanceof checks runtime type, while generics mainly provide compile-time type safety.'
  • 'instanceof List<String> does not compile because Java erases generic type arguments at runtime.'
  • 'instanceof List<?> is valid and usually preferable to raw List.'
  • If you need to verify element types, inspect the list contents explicitly.
  • Use instanceof carefully and prefer stronger type design when possible.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Object-Oriented Design practice on Codemia

Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.

Practice OOD

All Rights Reserved.