Type Patterns

A type pattern consists of a type along with a single pattern variable. It's used to test whether a value is an instance of the type appearing in the pattern.

Type Patterns with Reference Types

The pattern variable of a type pattern can be any reference type.

The following switch statement matches the selector expression obj with type patterns that involve a class type, an enum type, a record type, and an int[] array type:

record Point(int x, int y) { }
enum Color { RED, GREEN, BLUE; }
//...

static void typeTester(Object obj) {
    switch (obj) {
        case null     -> System.out.println("null");
        case String s -> System.out.println("String");
        case Color c  -> System.out.println("Color with " + c.values().length + " values");
        case Point p  -> System.out.println("Record class: " + p.toString());
        case int[] ia -> System.out.println("Array of int values of length" + ia.length);
        default       -> System.out.println("Something else");
    }
}

Type Patterns with Primitive Types

You can specify a primitive type instead of a reference type in a type pattern.

Note:

Primitive types in type patterns is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.

See JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview) for additional information.

The following example uses type patterns to test whether a value is a float, double, or int:

float v = 1000.01f;
        
if (v instanceof float f) System.out.println("float value: " + f);
if (v instanceof double d) System.out.println("double value: " + d);
if (v instanceof int i) System.out.println("int value: " + i);

It prints output similar to the following:

float value: 1000.01
double value: 1000.010009765625

See the section Decimal ↔ Binary Conversion Issues in the Double JavaDoc API documentation for an explanation why the converted double value is different than the original float value in this example.

Tip:

Using pattern matching with primitives is particularly useful for ensuring type safety when casting values. See Safe Casting with instanceof and switch for more information.

The case labels in the following switch expression use type patterns that involve float:

String floatToRating(float rating) {
    return switch(rating) {
        case 0f      -> "0 stars";
        case 2.5f    -> "Average";
        case 5f      -> "Best";
        case float f -> "Invalid rating: " + f;
    };
}

Type Patterns with Parameterized Types

As described in Safe Casting with instanceof and switch, you can use the instanceof operator to test if the right operand's type can be cast to left operand's type. This still applies if the right operand is a type pattern that contains one or more parameterized types. However, because of type erasure (see Type Erasure in The Java Tutorials), there's no parameterized type information at run time. Consequently, the runtime can't fully validate some casts between types that contain parameterized types. The compiler generates warnings or errors for these situations. However, the compiler will generate a error if it can't fully validate casts between instanceof operands that contain parameterized types.

Consider the following example that tries to cast a List to a List<String>:

List myList = null;
List<String> stringList = (List<String>) myList;

The compiler generates this warning with the -Xlint:unchecked option:

warning: [unchecked] unchecked cast
List<String> stringList = (List<String>) myList;
                                          ^
  required: List<String>
  found:    List

Consider the following example where the instanceof operator tests whether myList, a List, has the type List<String>:

List myList = null;
        
if (myList instanceof List<String> sl) {
    System.out.println("myList is a List<String>, size " + sl.size());
}

The compiler generates an error for the second instanceof expression:

error: List cannot be safely cast to List<String>
if (myList instanceof List<String> sl) {
    ^

The following contains additional examples of instanceof expressions containing type patterns with parameterized types.

public class Box<T> {
    private T t;    
    public Box() { }
    public Box(T t) { this.t = t; }
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

public class Shoebox<T> extends Box<T> {    
    public Shoebox(T t) { super(t); }
}
// ...

Shoebox<String> sb = new Shoebox<>("a pair of new shoes");

if (sb instanceof Box<String> s) {
    System.out.println("Box<String> contains: " + s.get());
}
        
if (sb instanceof Shoebox<String> s) {
    System.out.println("Shoebox<String> contains: " + s.get());
}
        
if (sb instanceof Shoebox<?> s) {
    System.out.println("Shoebox<?> contains: " + s.get());
}
        
if (sb instanceof Shoebox<Object> s) {
    // error: incompatible types: Shoebox<String> cannot be
    //        converted to Shoebox<Object>
    System.out.println("Shoebox<Object> contains: " + s.get());
}

The following expressions return true. The type parameters on both sides of the instanceof pattern matching operator are the same:

  • sb instanceof Box<String> s
  • sb instanceof Shoebox<String> s

The expression sb instanceof Shoebox<?> s returns true. The wildcard type (?) matches all types.

However, the compiler can't fully validate at runtime whether Shoebox<Object> can be cast to Shoebox<String> even though String is a subtype of Object. This parameterized type information won't exist at run time because of type erasure. Consequently, the compiler generates an error for the following expression:

sb instanceof Shoebox<Object> s

Note:

The previous example uses instanceof as the pattern matching operator. If you use instanceof as the type comparison operator, the same issues about parameterized types and type erasure still apply. For example, the compiler generates an error for this expression:
sb instanceof Shoebox<Object>