Java 16 Pattern Matching for instanceof

Introduction Every Java developer has written this sequence many times: test whether an object is of some type with instanceof, then cast it to that type, then use it - naming the type twice and adding a variable just to hold the cast result. Pattern matching for instanceof, previewed in Java 14 and 15 and finalized in Java 16Click to read tutorial: Overview of New Features in Java 16 (JEP 394), does all three in one step. This tutorial explains pattern matching for instanceof with examples. We will see the new syntax, how the scope of the pattern variable follows the logic of the code (flow scoping), how it simplifies equals() methods, and the rules the compiler enforces. The test-then-cast problem
Before and after pattern matching
//InstanceofDemo.java
public class InstanceofDemo {
  static String describeOld(Object obj) {
    if (obj instanceof String) {
      String s = (String) obj;                  // test, then cast to a new variable
      return "String of length " + s.length();
    }
    return "something else";
  }

  static String describe(Object obj) {
    if (obj instanceof String s) {              // test and bind in one step
      return "String of length " + s.length();
    } else if (obj instanceof Integer i && i > 100) {   // s is not in scope here; i can be used in the same condition
      return "large Integer " + i;
    } else if (obj instanceof int[] arr) {
      return "int array with " + arr.length + " elements";
    }
    return "something else: " + obj;
  }

  public static void main(String[] args) {
    System.out.println(describeOld("hello"));
    for (Object o : new Object[] {"hello", 250, 7, new int[] {1, 2, 3}}) {
      System.out.println(describe(o));
    }
  }
}
 OUTPUT of the above code
String of length 5
String of length 5
large Integer 250
something else: 7
int array with 3 elements
Explanation of the code
  • obj instanceof String s is a type pattern. If obj is a String, the test is true and obj, already cast, is assigned to the new pattern variable s.
  • In obj instanceof Integer i && i > 100, i can be used on the right of &&, because that part only runs when the match succeeded. So 250 is a "large Integer", while 7 fails the condition and falls through to the end.
  • Patterns work with any reference type, including arrays such as int[]. If obj is null, instanceof is false, as before, so a pattern variable is never null.
Flow scoping A pattern variable is in scope exactly where the compiler can be sure the match succeeded. This is called flow scoping, and it works with negation too: if a method returns early when the match fails, the pattern variable is in scope for the rest of the method -
Using a pattern variable after a negated test
//FlowScopingDemo.java
public class FlowScopingDemo {
  static int length(Object obj) {
    if (!(obj instanceof String s)) {
      return -1;                       // not a String: leave the method
    }
    return s.length();                 // here obj must be a String, so s is in scope
  }

  public static void main(String[] args) {
    System.out.println(length("pattern"));
    System.out.println(length(42));
  }
}
 OUTPUT of the above code
7
-1
The same reasoning makes s unavailable after || - in obj instanceof String s || s.isEmpty(), the right-hand side runs exactly when the match failed, so using s there is a compile error. Simpler equals() methods Pattern matching is especially welcome in equals(), which always tests and casts its argument -
equals() with pattern matching
//EqualsDemo.java
import java.util.Objects;

public class EqualsDemo {
  static final class Book {
    private final String isbn;
    private final String title;
    Book(String isbn, String title) { this.isbn = isbn; this.title = title; }

    @Override
    public boolean equals(Object o) {
      return o instanceof Book other
          && isbn.equals(other.isbn)
          && title.equals(other.title);
    }
    @Override public int hashCode() { return Objects.hash(isbn, title); }
  }

  public static void main(String[] args) {
    Book a = new Book("978-0134685991", "Effective Java");
    System.out.println(a.equals(new Book("978-0134685991", "Effective Java")));
    System.out.println(a.equals("Effective Java"));
    System.out.println(a.equals(null));
  }
}
 OUTPUT of the above code
true
false
false
The whole method is one expression: o instanceof Book other handles null, the type check and the cast at once.
Rules the compiler enforces
  • A pattern variable cannot have the same name as another variable in scope, such as a local variable or a parameter.
  • Using a pattern variable where the match may not have succeeded is an error.
  • The test must be able to fail. In Java 16, testing an expression against a type it always has, such as a String against String s, is an error, since the pattern would be pointless. (Java 21 relaxed this rule when it added pattern matching for switch.)
  • Testing against an impossible type, such as an Integer against String s, is an error, as it already was for plain instanceof.
Compiler errors for pattern variables
//PatternErrors.java
public class PatternErrors {
  static void check(Object obj, String text) {
    if (obj instanceof String text) { }               // name clash with a parameter
    if (obj instanceof String s || s.isEmpty()) { }   // s may not be bound here
    if (text instanceof String t) { }                 // always true
  }
}
 OUTPUT on compiling the above code
PatternErrors.java:4: error: variable text is already defined in method check(Object,String)
    if (obj instanceof String text) { }               // name clash with a parameter
                              ^
PatternErrors.java:5: error: cannot find symbol
    if (obj instanceof String s || s.isEmpty()) { }   // s may not be bound here
                                   ^
  symbol:   variable s
  location: class PatternErrors
PatternErrors.java:6: error: unconditional patterns in instanceof are not supported in -source 16
    if (text instanceof String t) { }                 // always true
             ^
  (use -source 21 or higher to enable unconditional patterns in instanceof)
3 errors
Note - pattern matching for instanceof was the first step of a larger plan. Pattern matching for switchClick to read: Pattern matching for switch and record patternsClick to read: record patterns, final in Java 21, extend the same idea to switch and to taking records apart. Summary In this tutorial we saw how pattern matching for instanceof, final in Java 16, replaces the test-then-cast idiom with a type pattern which tests, casts and binds a variable in one step. We used pattern variables in conditions with &&, saw how flow scoping makes a variable available after a negated test, and wrote an equals() method as a single expression. Lastly, we went through the rules the compiler enforces, with the errors for breaking them. Next, read about the Java 16 API additionsClick to read tutorial: Java 16 API Additions: Stream.toList, mapMulti and More.