Traps

92. Covariant Array Trap

Java arrays are covariant (String[] is an Object[]), meaning type-unsafe assignments compile but throw ArrayStoreException at runtime; generics are invariant and catch these errors at compile time.

Java 21 (core language)

Explanation

Array covariance: String[] strings = new String[3] can be assigned to Object[] objs = strings โ€” this compiles. But if you then objs[0] = Integer.valueOf(42), the JVM detects that the actual array type is String[] and throws ArrayStoreException. Every array write is guarded by a runtime type check โ€” performance overhead.

Generics are invariant: List<String> is NOT a List<Object>. List<Object> objs = (List<String>) strings is a compile error. This catches the bug at compile time. The cost: inflexibility when you want to pass a List<String> to a method that takes List<Object> โ€” you need bounded wildcards (List<? extends Object> or just List<?>).

The lesson: prefer List<T> over T[] in APIs to get compile-time type safety. Use arrays only in performance-critical low-level code or when required by an API.

Example

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

public class CovariantArrayTrap {
    public static void main(String[] args) {
        // Array covariance: compiles, but dangerous at runtime
        String[] strings = new String[3];
        Object[] objects = strings; // OK at compile time (covariance)
        objects[0] = "hello";       // OK โ€” String is compatible
        try {
            objects[1] = 42;        // ArrayStoreException! actual type is String[]
        } catch (ArrayStoreException e) {
            System.out.println("ArrayStoreException caught: " + e.getMessage());
        }

        // This can hide bugs: method accepts Object[], writes wrong type
        static_fillArray(new String[3]); // passes Object[] โ€” trap!

        // Generics are invariant: compile-time safety
        List<String> stringList = new ArrayList<>();
        // List<Object> objectList = stringList; // COMPILE ERROR โ€” invariant!
        List<?> wildcard = stringList; // OK โ€” unbounded wildcard (read-only)
        System.out.println("Wildcard size: " + wildcard.size());

        // PECS: correct way to accept any List
        List<String> names = List.of("Alice", "Bob");
        printAll(names); // OK with List<? extends Object>
    }

    static void static_fillArray(Object[] arr) {
        arr[0] = "string"; // OK
        arr[1] = 42;       // ArrayStoreException if arr is String[]!
    }

    // Correct: generic method is compile-time safe
    static void printAll(List<?> items) {
        items.forEach(System.out::println);
    }
}

Sources