Back to Common Runtime Exceptions
Q2Question 2 of 4

What causes ClassCastException?

Key Concepts
  • ClassCastException occurs when the actual object type does not match the cast at runtime
  • Three main causes: invalid downcast without instanceof, heap pollution from raw types, and toArray() returning Object[]
  • Fix invalid downcasts with Java 16 pattern matching: if (obj instanceof String s) eliminates separate cast
  • Fix heap pollution by never using raw types: always use List<String> not List
  • Fix toArray by using list.toArray(new String[0]) or list.toArray(String[]::new)
  • Modern Java sealed interfaces with switch pattern matching eliminate manual downcasting entirely

Definition

ClassCastException occurs when you cast an object to a type it is not, commonly from invalid downcasts, raw-type heap pollution, or toArray() misuse, and is prevented by instanceof checks and proper generics.
  • ClassCastException is thrown at runtime when you cast an object to a type it is not: (Integer) stringObj.
  • Three main causes: (1) Invalid downcast without instanceof check. (2) Heap pollution from mixing raw types and generics. (3) toArray() returning Object[] which cannot be cast to String[].
  • Fix: use instanceof before casting (Java 16 pattern matching: if (obj instanceof String s)), use proper generics (no raw types), use toArray(new String[0]) or toArray(String[]::new).

At National Bank of India, you have a safe deposit box labeled "Account" (reference type). Inside is actually a SavingsAccount. You try to open it as a LoanAccount and the system rejects the access. Always check the contents before treating them as a specific type.

Understanding the Concepts

What causes ClassCastException?

The most common cause is an invalid downcast where the actual object type does not match the target type.

JAVA
package com.bank.demo;

public class ClassCastDemo {

    // CAUSE 1: invalid downcast
    public static void invalidDowncast() {
        Object obj = "Hello";
        try {
            Integer num = (Integer) obj;  // String is NOT Integer
        } catch (ClassCastException e) {
            System.out.println("CCE: " + e.getMessage());
        }
    }

    // FIX: instanceof check (Java 16 pattern matching)
    public static void safeDowncast() {
        Object obj = "Hello";
        if (obj instanceof String s) {
            System.out.println("Safe: " + s.toUpperCase());
        } else {
            System.out.println("Not a String");
        }
    }

    // CAUSE 2: toArray() returns Object[]
    public static void toArrayMisuse() {
        var accounts = java.util.List.of("SAV2001", "CUR3001");
        Object[] objArray = accounts.toArray();
        try {
            String[] strArray = (String[]) objArray;  // CCE!
        } catch (ClassCastException e) {
            System.out.println("toArray CCE: Object[] cannot cast to String[]");
        }
        // FIX:
        String[] correct = accounts.toArray(new String[0]);
        System.out.println("Fixed: " + java.util.Arrays.toString(correct));
    }

    public static void main(String[] args) {
        invalidDowncast();
        safeDowncast();
        toArrayMisuse();
    }
}
TEXT
CCE: class java.lang.String cannot be cast to class java.lang.Integer
Safe: HELLO
toArray CCE: Object[] cannot cast to String[]
Fixed: [SAV2001, CUR3001]

What is heap pollution?

Heap pollution occurs when raw types allow incompatible objects into a generic collection. The CCE happens at get(), not at insertion.

JAVA
package com.bank.demo;

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

public class HeapPollutionDemo {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        List rawList = new ArrayList();  // raw type, no generic
        rawList.add("Rajesh");
        rawList.add(42);  // Integer added, no compile error

        List<String> names = rawList;  // unchecked assignment
        System.out.println("First: " + names.get(0));  // OK
        try {
            String second = names.get(1);  // CCE! Integer, not String
        } catch (ClassCastException e) {
            System.out.println("Heap pollution: " + e.getMessage());
        }

        // FIX: never use raw types
        List<String> safe = new ArrayList<>();
        safe.add("Rajesh");
        // safe.add(42);  // COMPILE ERROR with generics
        System.out.println("Safe: " + safe.get(0));
    }
}
TEXT
First: Rajesh
Heap pollution: class java.lang.Integer cannot be cast to class java.lang.String
Safe: Rajesh

What should a senior engineer know?

  • Java 21 sealed + switch. Sealed interfaces with pattern matching switch eliminate downcasting entirely. The compiler ensures exhaustive handling.
  • Generics at runtime. Due to type erasure, List and List are the same type at runtime. Generics prevent CCE at compile time, not runtime.
  • Collections.checkedList(). Wraps a list with runtime type checks: Collections.checkedList(list, String.class) throws CCE on insertion of wrong type.

What are common interview follow-up questions?

  • Why does toArray() return Object[]? (Due to type erasure, the list does not know its element type at runtime.)
  • How does instanceof pattern matching help? (Check and cast in one step, no separate cast statement.)
  • What is heap pollution? (Raw types allowing wrong types into generic collections.)

What are common mistakes?

A common mistake is not understanding the concept fully before using it in production banking applications.

Wrong approach:

JAVA
package com.bank.service;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, double amount) {
        // WRONG: using double for money
        double balance = getBalance(fromAccount);
        balance = balance - amount;
    }
    private double getBalance(String id) { return 10000.0; }
}

Correct approach:

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        // CORRECT: using BigDecimal for money
        BigDecimal balance = getBalance(fromAccount);
        balance = balance.subtract(amount);
    }
    private BigDecimal getBalance(String id) { return new BigDecimal("10000.00"); }
}

Always use BigDecimal for monetary calculations in banking systems to avoid floating-point precision errors.

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

Visual Overview

DIAGRAM
  ClassCastException causes:

  1. Invalid downcast:     (Integer) stringObj    → CCE
     Fix: if (obj instanceof Integer i) { ... }

  2. Heap pollution:       raw List + generic List → CCE at get()
     Fix: always use generics, never raw types

  3. toArray() misuse:     (String[]) list.toArray() → CCE
     Fix: list.toArray(new String[0])

Key Takeaway

ClassCastException occurs at runtime when the actual object type does not match the cast. The three main causes are invalid downcasts (casting without instanceof check), heap pollution from raw types (mixing generic and raw collections lets incompatible objects sneak in), and toArray() returning Object[] which cannot be cast to String[].

Prevention: use Java 16 pattern matching if (obj instanceof String s) for safe downcasting. Never use raw types. Use toArray(new String[0]) or toArray(String[]::new). Modern Java with sealed interfaces and switch pattern matching eliminates the need for manual downcasting entirely.

At National Bank of India, the transaction processor blindly cast (Deposit) transaction for every incoming transaction. When a Withdrawal arrived, ClassCastException crashed the processor and 500 transactions were stuck. Fix: pattern matching or instanceof before every downcast.

Remember one sentence: always check with instanceof before downcasting, never use raw types, and use toArray(new String[0]) instead of casting toArray().

---

Share
Use ← → arrow keys to navigate