skip to content

Array Covariance and ArrayStoreException

String[] is assignable to Object[], which is why every array store carries a runtime type check and can throw ArrayStoreException. Interviewers pair it with invariant generics to explain why List<String> is not a List<Object>.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

4

What is ArrayStoreException and when exactly is it thrown?

level: middleimportance: must knowfreq 62%

answer

  1. Unchecked RuntimeException, thrown at the STORE not the assignment
  2. Caused by covariance: writing a wrong type through a wider array reference
  3. JVM checks every reference-array store against the real component type
  4. Message = class of the value you tried to store
  5. null and primitive arrays never trigger it

basics

~20 s

ArrayStoreException is a runtime error thrown when you try to store an element into an array whose real component type can't hold it — for example storing an Integer into a String[] that you accessed through an Object[] reference.

solid answer

~50 s

ArrayStoreException is an unchecked (RuntimeException) thrown at runtime when you store a reference into an array element that is incompatible with the array's actual runtime component type. It exists because arrays are covariant: you can hold a String[] through an Object[] variable, and through that variable the compiler allows storing any Object. Every reference array store therefore carries a runtime check — the JVM verifies the value being stored is assignment-compatible with the array's real component type, and throws ArrayStoreException if not. The classic trigger is: assign a String[] to an Object[] variable, then write a non-String (e.g. an Integer) through the Object[] reference. The compiler is happy; the JVM throws at the store. This is the cost the language pays to keep covariant arrays from corrupting the heap. The check applies only to reference-type arrays; primitive arrays can't hit it.

code

java · 7 lines
java
Object[] arr = new String[2]; // covariant: legal
arr[0] = "ok";               // String into String[] -> fine
arr[1] = 42;                  // Integer into String[] -> ArrayStoreException
// Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer

String[] s = new String[2];
s[0] = null;                  // null is always allowed, never throws

go deeper

for a junior

Recognizes ArrayStoreException as a runtime error from putting the wrong type into an array and can read the stack trace.

for a middle

Explains it results from covariance, that it's unchecked and thrown at the store, and can write a minimal reproduction.

for a senior

Explains the per-store JVM check, the null/primitive exceptions to the rule, the performance cost, and where it surfaces in library APIs.

for a principal

Connects it to type-system soundness (covariant arrays are unsound; the runtime check restores heap safety) and guides API/design choices toward invariant generics and defensive array handling.

## The setup Recall that Java arrays are **covariant**: because `String` is a subtype of `Object`, `String[]` is a subtype of `Object[]`, so you can do: ```java String[] strings = new String[3]; Object[] objects = strings; // both references point at the SAME array ``` The variable `objects` has *compile-time* (static) type `Object[]`, but the object it points to is, at *runtime*, really a `String[]`. The array object remembers its own **component type** (`String`) — that information lives in the array object on the heap, not in the variable. ## Where it goes wrong Through an `Object[]` reference the compiler will let you store **any** `Object`: ```java objects[0] = 42; // autoboxed to Integer; compiles fine — Integer IS-A Object ``` The compiler only knows `objects` is an `Object[]`, and an `Integer` is a valid `Object`, so it raises no error. But the *actual* array is a `String[]`; an `Integer` does not belong there. If this store were allowed, `strings[0]` would later be read as a `String` and explode. To prevent that heap corruption, **the JVM performs a runtime type check on every store into a reference array**: it checks that the value's class is assignment-compatible with the array's real component type. When the check fails it throws: ``` java.lang.ArrayStoreException: java.lang.Integer ``` (The message is the class of the *value* you tried to store.) ## Key facts - **Unchecked exception.** `ArrayStoreException extends RuntimeException` — you don't have to declare or catch it; it signals a programming error. - **Thrown at the store, not at the assignment.** `Object[] o = strings;` is fine; the throw happens on `o[i] = badValue;`. - **Reference arrays only.** Primitive arrays (`int[]`, `double[]`) can't produce it — there's no covariance and the element type is fixed. - **`null` never triggers it.** Storing `null` is always allowed (null is a valid value of any reference type). - **The check has a (small) runtime cost** on array stores, which is a reason high-performance code and the language designers preferred invariant generics for new collection types. ## A full reproducible example ```java Object[] arr = new String[2]; // covariant assignment arr[0] = "ok"; // fine: String into String[] arr[1] = Integer.valueOf(7); // ArrayStoreException at runtime ``` ## Where it bites in real code `Arrays.asList(...)`, `System.arraycopy`, `Collection.toArray(T[])`, and generic methods that receive a `T[]` can all surface this when the caller's actual array component type is narrower than the static type the method writes through. ## How generics avoid it Generics are **invariant**: `List<String>` is not a `List<Object>`, so the analogous unsafe store is rejected **at compile time** and no runtime store check is needed. This is why preferring `List<T>` over `T[]` is standard advice.

  • Why does Java throw at runtime instead of catching this at compile time?
    Because the array's real component type is only known at runtime — the static type of the reference (Object[]) hides it. The compiler sees a legal Object-into-Object[] store; only the JVM, which knows the array is actually a String[], can detect the mismatch.
  • Does storing null into a covariant array ever throw ArrayStoreException?
    No. null is assignment-compatible with every reference type, so the store check always passes for null regardless of the array's component type.

saying these in an interview costs you the question

  • Saying it's a checked exception that must be caught
  • Thinking the throw happens at the covariant assignment rather than at the element write
  • Believing primitive arrays can throw it
  • Assuming the compiler catches the bad store (it cannot — that's the whole point)

context

open as a page

Why are Java generics invariant when arrays are covariant, and what practical difference does that make?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Generics are invariant: List<String> is not a List<Object>, so a bad store is caught at compile time. Arrays are covariant, so the same kind of bad store compiles and only fails at runtime with ArrayStoreException.

open as a page

What does it mean that Java arrays are covariant, and why is String[] assignable to Object[]?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Covariance means that if String is a subtype of Object, then String[] is treated as a subtype of Object[]. So you can assign a String[] to an Object[] variable and Java accepts it at compile time.

open as a page

What runtime mechanism makes covariant array stores safe, and what are its costs and design implications?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

The JVM checks every store into a reference array against the array's real element type, throwing ArrayStoreException on a mismatch. This costs a small per-store check and is part of why invariant generics were preferred for new APIs.

open as a page