W3docs

Java instanceof Operator

Test an object's runtime type with the instanceof operator and use pattern matching for instanceof.

instanceof asks a runtime question: "is this reference actually a T (or a subtype of T)?" The answer is a boolean, and the modern pattern-matching form will also bind the value to a typed variable in one step, so you don't need a separate cast.

It's the tool you reach for whenever you have a polymorphic reference and need to ask which concrete kind it really is — typically inside equals, when walking a heterogeneous data structure, or when working with sealed hierarchies.

The basic form

The classical syntax is expression instanceof Type:

Object o = "hello";
if (o instanceof String) {
  String s = (String) o;
  System.out.println(s.length());
}

The check returns true if o refers to a String or any subtype, and false if o is null or points to a different kind of object. null instanceof Anything is always false — a small but useful guarantee.

Pattern matching for instanceof

Since Java 16, instanceof accepts a type pattern that declares a variable bound to the narrowed type in the same expression:

if (o instanceof String s) {
  System.out.println(s.length());   // no cast needed
}

If the check succeeds, s is in scope and already typed as String. If it fails, s is not in scope. The cast is gone, the redundancy is gone, and you can't accidentally cast something the check rejected.

Scope of the bound variable

The bound variable is in scope wherever the compiler can prove the check succeeded. That includes the if branch, but it also flows through && and into the negation of the if:

if (o instanceof String s && s.length() > 3) { ... }   // s is in scope after && — also a String

if (!(o instanceof String s)) return;
System.out.println(s.length());   // s is in scope after the early return

That second pattern is especially nice for guard-clause style code — narrow the type, bail if it didn't match, use the bound name freely below.

Restrictions on the target type

The compiler rejects checks it can prove are impossible. "hello" instanceof Integer won't even compile, because String and Integer are unrelated. This catches typos and refactor leftovers at build time rather than runtime.

It also rejects upcasts that can't fail: Object o = ...; if (o instanceof Object) {} is flagged as redundant.

In switch

The same pattern-matching machinery is available in switch, which is where it really shines on sealed hierarchies:

String describe(Object o) {
  return switch (o) {
    case Integer i  -> "int " + i;
    case String  s  -> "str of length " + s.length();
    case int[]   a  -> "array of " + a.length;
    case null       -> "nothing";
    default         -> "something else";
  };
}

With a sealed type as the selector, you can drop the default and the compiler will demand a case for every permitted subtype — pair this with sealed classes to get exhaustive case analysis.

When to use it — and when not

Use instanceof when the type is genuinely unknown and the answer changes behavior: implementing equals, processing tagged-union sealed hierarchies, walking AST nodes. Don't use it as a substitute for proper polymorphism — a chain of if (x instanceof A) ... else if (x instanceof B) ... over an open hierarchy is usually a sign that a virtual method on the type would do the job better.

A worked example

java— editable, runs on the server

What's next

instanceof is one of several methods every Java object responds to. The next chapter zooms out to look at the whole bundle — java.lang.Object, the root class every type silently extends, and the methods you inherit from it whether you wanted them or not. Continue to Java Object class.

Practice

Practice

What does pattern matching for `instanceof` (`if (o instanceof String s)`) save you compared to the classical form?