13 Safe Casting with instanceof and switch

You can use the instanceof operator to check at run time whether a cast from one type to another is safe. A cast or conversion is safe if there's no loss of information or exception thrown during the cast or conversion. You can use the instanceof operator to test whether a cast is safe between two reference types or two primitive types. In addition, conversion safety affects how switch statements and expressions behave.

Note:

The ability to use the instanceof operator to test whether a cast is safe between two primitive types 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 instanceof as the type comparison operator to test whether the Shape s is a Circle. If it is, then it's safe to cast Shape s to a Circle, which means that there's definitely a radius that you can print:

public interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
//...

Shape s = new Rectangle(4,3);
if (s instanceof Circle) {
    Circle c = (Circle)s;
    System.out.println("Radius: " + c.radius());
}

The Java runtime throws a ClassCastException if it can't cast a reference type to another reference type. If you remove the s instanceof Circle expression from the example, the Java runtime throws an exception when it tries to cast the Shape, which is a Rectangle, to a Circle:

Shape s = new Rectangle(4,3);

// ClassCastException: class Rectangle cannot be cast
// to class Circle
Circle c = (Circle)s;

System.out.println("Radius: " + c.radius());

When it comes to casting between primitive types, the Java runtime won't ever throw a ClassCastException. It will convert the value (if the types are compatible), which might result in the loss of information about the value's overall magnitude as well as its precision and range. The following example casts an int to a byte:

int i = 2345323;
byte b = (byte)i;
System.out.println(b);

It prints the following output:

107

An improperly or unintendedly converted value, such as from 2345323 to 107, or more subtly, from 2.05f to 2, can silently propagate through a program, causing potentially elusive bugs.

As the primitive types int and byte differ by their ranges, you could test that before casting the value:

if (i >= -128 && i <= 127) {
    System.out.println((byte)i);
}

However, a clearer and more concise way to test whether a cast between two primitive type is safe is to use instanceof as the type comparison operator:

if (i instanceof byte) {
    System.out.println((byte)i);
}

Note that this example still has to cast the value i to a byte. The instanceof type comparison operator only checks if i is a byte. It doesn't perform the actual cast.

This also works with instanceof as the pattern match operator, which has the added benefit of performing the cast for you if the test is successful:

if (i instanceof byte b) {
    System.out.println(b);
}

Conversion Safety and switch

Consider the following example, which contains two switch statements. The compiler generates an error for the first switch statement:

float f = 100.01f;

switch (f) {
    // error: the switch statement does not cover
    // all possible input values
    case int i ->
        System.out.println(i + " as an int ");
}

switch (f) {
    case double d ->
        System.out.println(d + " as a double");
}

The first switch statement isn't exhaustive because it only processes int values. The case label case int i converts the float value of the switch statement's selector expression to an int value. This conversion is not safe, which means that there's a possibility that information may be lost about the value's overall magnitude as well as its precision and range. If this switch statement were permitted, then an improperly or unintendedly converted value, such as from 100.01f to 100, can propagate through a program, causing subtle bugs. To help prevent this from happening, the compiler generates an error.

The second switch statement is exhaustive. The label double d converts the selector expression's float to a double. This is an unconditionally exact conversion, which means that, at compile time, it's known that the conversion from the first type to the second type is guaranteed not to lose information or throw an exception for any value. Examples of unconditionally exact conversions include converting from byte to int, from byte to Byte, from int to long, and from String to Object.

Whether a conversion between primitive types is unconditionally exact also affects how patterns can dominate other patterns. (See Pattern Label Dominance.) In the following example, the compiler generates an error for the type pattern byte b:

switch (f) {
    case int i ->
        System.out.println(i + " as an int");
    // error: this case label is dominated by a preceding case label
    case byte b ->
        System.out.println(b + " as a byte");
    default ->
        System.out.println(f + " as a float");
}

The type pattern int i dominates the type pattern byte b because converting from byte to int is unconditionally exact.