Blog |

Handling the ClassCastException Runtime Exception in Java

Handling the ClassCastException Runtime Exception in Java
Table of Contents

Introduction to Runtime Exceptions

Runtime exceptions are exceptions which can not be checked at compile time. In Java, there are a myriad of classes derived from the RuntimeException class [1], all of which represent unchecked exceptions that need to be carefully considered and managed. Despite being less serious and critical than the unchecked runtime errors [2], these exceptions can still be very problematic and cause unexpected issues at runtime, especially if necessary precautions aren’t taken and relevant exception handling mechanisms aren’t put in place.

 

What is ClassCastException and When does it Happen?

As its name implies, ClassCastException is an exception that happens when the JVM tries to cast an object to a class (or in some instances, an interface) and fails. This relates to explicit type casting [3] and the reason the cast fails can be traced to an attempt at downcasting an object to a class of which it is not an instance, or to an interface which it does not implement.

ClassCastException is a subclass of the RuntimeException class which means it is an unchecked, runtime exception [4]. This exception can not be checked at compile-time because the compiler has no way of knowing whether the object is actually an instance of the target subclass, or if it is an instance of a subclass that implements the target interface. Consequently, if either of these scenarios is encountered at runtime, Java will throw the ClassCastException exception.

Parent parent = new Child();
/*...*/
Child c = (Child) parent; // is parent actually an instance of Child?
IFace i = (IFace) parent; // Is parent an instance of a subclass that implements IFace?

The only scenario where the compiler is able to detect invalid type casts of this kind is when the source type is a final class and it neither extends nor implements the target type, because it is known in advance that the final class does not have any subtypes, i.e., it cannot be subclassed [5].

String s = "s";
IFace i = (IFace) s; // compilation error (the String class is final)

 

How to handle ClassCastException

To prevent the ClassCastException exception, one should be careful when casting objects to a specific class or interface and ensure that the target type is a child of the source type, and that the actual object is an instance of that type. To help achieve type safety and catch these issues at compile time, two builtin Java mechanisms are available:

  1. the instanceof operator, and
  2. Generics.

 

ClassCastException Examples

To better understand ClassCastException, consider the following Java class hierarchy:

class X {/*...*/}
class Y extends X {/*...*/}
class Z extends X {/*...*/}
Object o = new Z(); // OK
X x = new Y(); // OK
Y y = (Y) x; // OK
y = (Y) o; // Will throw ClassCastException
y = (Y) new X(); // Will throw ClassCastException
Z z = (Z) x; // Will throw ClassCastException

The resulting scenarios can be summarized as follows:

  • It is possible to cast an instance of X, Y, or Z, to Object, since all Java classes implicitly inherit the java.lang.Object class [6].
  • It is possible to cast an instance of Y or Z to X, because they are both subtypes of X.
  • It is possible to cast an instance of type X to type Y (or Z) ONLY if the original object is of type Y (or Z), due to polymorphism [7].
  • It is impossible to cast an instance of Y to Z despite the fact that they are both derived from X, because Y and Z are unique types with distinct states and behaviors.

Complete examples and ways to deal with ClassCastException are presented below.

 

Using the instanceof operator

Java’s instanceof operator is a binary operator used to test whether the object is an instance of a specific class, or a class that implements a specific interface [8]. When used in the appropriate context, this operator can prevent the ClassCastException exception from occurring. The code example below shows how trying to cast an instance of Phone to a subclass of Phone (Smartphone) throws the ClassCastException exception.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package rollbar;

class Phone {}

interface Wireless {
  default void charge() {
    System.out.println("Phone is charging");
  }
}

class Smartphone extends Phone implements Wireless {}

public class ClassCastExceptionExample {

  private static final Phone myPhone = new Phone();

  public static void main(String... args) {
    Smartphone wirelessPhone = (Smartphone) myPhone;
    wirelessPhone.charge();
  }
}
Exception in thread "main" java.lang.ClassCastException: class rollbar.Phone cannot be cast to class rollbar.Smartphone
    at rollbar.ClassCastExceptionExample.main(ClassCastExceptionExample.java:19)

Casting an object to an interface is also a valid polymorphic operation, so one might try to cast the myPhone variable to a Wireless instance instead. However, since myPhone is not an instance of any class that implements Wireless, the ClassCastException is thrown again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package rollbar;

class Phone {}

interface Wireless {
  default void charge() {
    System.out.println("Phone is charging");
  }
}

class Smartphone extends Phone implements Wireless {}

public class ClassCastExceptionExample {

  private static final Phone myPhone = new Phone();

  public static void main(String... args) {
    Wireless wirelessPhone = (Wireless) myPhone;
    wirelessPhone.charge();
  }
}
Exception in thread "main" java.lang.ClassCastException: class rollbar.Phone cannot be cast to class rollbar.Wireless
    at rollbar.ClassCastExceptionExample.main(ClassCastExceptionExample.java:19)

The solution here is to use the instanceOf operator which will enforce a safe type cast, as shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package rollbar;

class Phone {}

interface Wireless {
  default void charge() {
    System.out.println("Phone is charging");
  }
}

class Smartphone extends Phone implements Wireless {}

public class ClassCastExceptionExample {

 private static final Phone myPhone = new Phone();

 public static void main(String... args) {
    if (myPhone instanceof Smartphone smartphone) {
      smartphone.charge();
    } else {
      System.out.println("Phone cannot be charged.");
    }
 }
}
Phone cannot be charged.

The same concept applies to interfaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package rollbar;

class Phone {}

interface Wireless {
  default void charge() {
    System.out.println("Phone is charging");
  }
}

class Smartphone extends Phone implements Wireless {}

public class ClassCastExceptionExample {

  private static final Phone myPhone = new Phone();

  public static void main(String... args) {
    if (myPhone instanceof Wireless wirelessPhone) {
      wirelessPhone.charge();
    } else {
      System.out.println("Phone cannot be charged.");
    }
  }
}
Phone cannot be charged.

Since myPhone is neither an instance of Smartphone nor an instance of a class that implements Wireless, the instanceOf operator inside the if statement evaluates to false, and the corresponding else clause is executed.

On the other hand, if an object passes the instanceOf check, then it can be safely cast to the specified type. This can be observed in the example below where the myPhone variable is an actual instance of the Smartphone class (as initialized on line 16).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package rollbar;

class Phone {}

interface Wireless {
  default void charge() {
    System.out.println("Phone is charging");
  }
}

class Smartphone extends Phone implements Wireless {}

public class ClassCastExceptionExample {

  private static final Phone myPhone = new Smartphone();

  public static void main(String... args) {
    if (myPhone instanceof Wireless wirelessPhone) {
      wirelessPhone.charge();
    } else {
      System.out.println("Phone cannot be charged.");
    }
  }
}
Phone is charging.

As a side note, older versions of Java which don’t support pattern matching for the instanceOf operator [9] will require an extra step to cast the object manually, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package rollbar;

class Phone {}

interface Wireless {
  default void charge() {
    System.out.println("Phone is charging");
  }
}

class Smartphone extends Phone implements Wireless {}

public class ClassCastExceptionExample {

  private static final Phone myPhone = new Smartphone();

  public static void main(String... args) {
    if (myPhone instanceof Wireless) {
      ((Wireless) myPhone).charge();
    } else {
      System.out.println("Phone cannot be charged.");
    }
  }
}
Phone is charging.

 

Using Generics & Parameterized Types

Introduced in Java 5, Generics are a very important addition to Java’s type system which brought compile-time type safety and eliminated the need for the tedious type casting when working with the Collections Framework [10]. This mechanism allows programmers to implement generic data structures and algorithms that are type-safe, and it allows Java compilers to perform strong type checking and detect related issues at compile-time.

A parameterized type is an instantiation of a generic type with an actual type argument. The code below shows how the use of raw, unparameterized collections such as Lists can easily lead to the ClassCastException being triggered. This is because unparameterized collections default to the Object type, so nothing prevents a program or an API from inserting an instance of an unexpected type into a collection. The example below shows how inserting and later trying to cast the string “200” into a List instance throws the ClassCastException exception.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package rollbar;

import java.util.ArrayList;
import java.util.List;

public class ClassCastExceptionGenerics {

  private static final List integerList = new ArrayList<>();

  public static void main(String... args) {
    integerList.add(100);
    integerList.add(150);
    integerList.add("200");

    integerList.forEach(o -> printRoot((Integer) o));
  }

  public static void printRoot(Integer number) {
    if (number != null) {
      System.out.println(Math.sqrt(number));
    }
  }
}
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer
    at rollbar.ClassCastExceptionGenerics.lambda$main$0(ClassCastExceptionGenerics.java:15)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at rollbar.ClassCastExceptionGenerics.main(ClassCastExceptionGenerics.java:15)

Using Generics to make the List parameterized restricts the types of objects the list can hold to valid instances of Integer, which in turn makes any attempt to insert any other, incompatible type in the list detectable at compile-time, as shown in the revised example below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package rollbar;

import java.util.ArrayList;
import java.util.List;

public class ClassCastExceptionGenericsFixed {

  private static final List<Integer> integerList = new ArrayList<>();

  public static void main(String... args) {
    integerList.add(100);
    integerList.add(150);
    integerList.add("200");

    integerList.forEach(o -> printRoot((Integer) o));
  }

  public static void printRoot(Integer number) {
    if (number != null) {
      System.out.println(Math.sqrt(number));
    }
  }
}
ClassCastExceptionGenerics.java:13: error: incompatible types: String cannot be converted to Integer
    integerList.add("200");
                    ^
1 error

Furthermore, using parameterized types to instantiate Generics eliminates the need to cast collection objects manually, so a working version of the example above could look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package rollbar;

import java.util.ArrayList;
import java.util.List;

public class ClassCastExceptionGenerics {

  private static final List<Integer> integerList = new ArrayList<>();

  public static void main(String... args) {
    integerList.add(100);
    integerList.add(150);
    integerList.add(200);

    integerList.forEach(o -> printRoot(o));
  }

  public static void printRoot(Integer number) {
    if (number != null) {
      System.out.println(Math.sqrt(number));
    }
  }
}
10.0
12.24744871391589
14.142135623730951

 

Conclusion

Runtime exceptions are an inevitable evil that all Java programmers have to face at some point. One of these exceptions is the ClassCastException which is thrown whenever there is an attempt to cast an object to a class or an interface the object is incompatible with. As with other runtime exceptions, being prudent is important and pays off in the long run. This article explains what causes the ClassCastException by diving into Java’s type casting rules, and it shows how to prevent and effectively deal with this exception by relying on the instanceof operator and using generic, parameterized types when the situation calls for it.

 

Track, Analyze and Manage Errors With Rollbar

Managing Java errors and exceptions in your code is challenging. It can make deploying production code an unnerving experience. Being able to track, analyze, and manage errors in real-time can help you to proceed with more confidence. Rollbar automates error monitoring and triaging, making fixing Java errors easier than ever. Sign Up Today!

 

References

[1] Oracle, 2021. RuntimeException (Java SE 17 & JDK 17). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html. [Accessed Jan. 21, 2022]

[2] Oracle, 2021. Error (Java SE 17 & JDK 17). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Error.html. [Accessed Jan. 21, 2022]

[3] Rollbar, 2022. How to Handle the Incompatible Types Error in Java. Rollbar Editorial Team. [Online]. Available: https://rollbar.com/blog/how-to-handle-the-incompatible-types-error-in-java/. [Accessed Jan. 21, 2022]

[4] Oracle, 2021. Unchecked Exceptions — The Controversy (The Java™ Tutorials > Essential Java Classes > Exceptions). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html. [Accessed Jan. 21, 2022]

[5] Oracle, 2021. Writing Final Classes and Methods (The Java™ Tutorials > Learning the Java Language > Interfaces and Inheritance). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/javase/tutorial/java/IandI/final.html. [Accessed Jan. 21, 2022]

[6] Oracle, 2021. Inheritance (The Java™ Tutorials > Learning the Java Language > Interfaces and Inheritance). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html. [Accessed Jan. 21, 2022]

[7] Oracle, 2021. Polymorphism (The Java™ Tutorials > Learning the Java Language > Interfaces and Inheritance). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html. [Accessed Jan. 21, 2022]

[8] Oracle, 2021. Equality, Relational, and Conditional Operators (The Java™ Tutorials > Learning the Java Language > Language Basics). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op2.html. [Accessed Jan. 21, 2022]

[9] G. Bierman, 2021. JEP 394: Pattern Matching for instanceof. Oracle and/or its affiliates. [Online]. Available: https://openjdk.java.net/jeps/394. [Accessed Jan. 21, 2022]

[10] Oracle, 2021. Why Use Generics? (The Java™ Tutorials > Learning the Java Language > Generics (Updated)). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/javase/tutorial/java/generics/why.html. [Accessed Jan. 21, 2022]

Related Resources

"Rollbar allows us to go from alerting to impact analysis and resolution in a matter of minutes. Without it we would be flying blind."

Error Monitoring

Start continuously improving your code today.

Get Started Shape