skip to content

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

level: juniorimportance: should knowfreq 55%

answer

  1. co = same direction: element subtype -> array subtype
  2. String[] IS-A Object[] (arrays are covariant)
  3. Designed pre-generics for Arrays.sort/System.arraycopy
  4. Safe to read, unsafe to write
  5. Generics are invariant by contrast

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.

solid answer

~40 s

Java arrays are covariant: the subtype relationship between element types carries over to the array types. Because String is a subtype of Object, String[] is a subtype of Object[], so you can pass a String[] where an Object[] is expected and assign one to the other. This was a deliberate language design choice in early Java (pre-generics) so that general-purpose methods like Arrays.sort(Object[]) or System.arraycopy could work across any array of reference types without overloads for each element type. The tradeoff is that covariance is not type-safe for writes: through the Object[] reference you could try to store an element that is not a String, which the compiler cannot catch. Java therefore enforces a runtime check on every array store, throwing ArrayStoreException when the actual element does not fit the array's real component type.

go deeper

for a junior

Can state that String[] can be assigned to Object[] and give a simple example that compiles.

for a middle

Explains the term covariance, why it was chosen (pre-generics library reuse), and notes the read-safe/write-unsafe asymmetry.

for a senior

Contrasts covariant arrays with invariant generics, names concrete JDK methods that rely on it, and articulates the design tradeoff.

for a principal

Discusses the historical/language-design rationale, how it interacts with the type system soundness gap closed by the runtime store check, and guidance for API design (prefer collections/generics over arrays in new code).

## What 'covariance' means **Subtyping** is the rule that says where a value of type `A` is expected, a value of a subtype of `A` is also accepted. In Java, `String` is a subtype of `Object` (every String *is an* Object). A `Cat` is a subtype of `Animal`. Now ask: given that `String` is a subtype of `Object`, what is the relationship between the *array* types `String[]` and `Object[]`? There are three possible design answers: - **Covariant** — the array relationship follows the element relationship in the *same* direction: `String[]` is a subtype of `Object[]`. ("co" = same direction.) - **Contravariant** — the opposite direction. - **Invariant** — no relationship at all: `String[]` and `Object[]` are unrelated, even though their elements are related. **Java chose covariant for arrays.** That is exactly why this compiles: ```java String[] strings = { "a", "b" }; Object[] objects = strings; // legal: String[] IS-A Object[] ``` `objects` and `strings` now point at the *same array object* on the heap. ## Why the designers did this Java 1.0 (1996) had **no generics** (those arrived in Java 5, 2004). Without covariance, a method that wanted to operate on "an array of any reference type" — sort it, copy it, print it — would need a separate overload for every possible element type, which is impossible since user types are unknown. Covariance let the standard library write one method against `Object[]` and have it accept `String[]`, `Integer[]`, `Person[]`, etc.: ```java static void printAll(Object[] arr) { // one method... for (Object o : arr) System.out.println(o); } printAll(new String[]{ "x" }); // ...works for any array ``` Real examples in the JDK: `Arrays.sort(Object[])`, `Arrays.fill(Object[], Object)`, `System.arraycopy(Object, int, Object, int, int)`. ## The catch Covariance is safe for *reading* (anything you read out of an `Object[]` really is an Object). It is **unsafe for writing**: through the `Object[]` view you could try to store, say, an `Integer` into what is physically a `String[]`. The compiler can't catch this because, as far as it knows, you're storing an Object into an Object[]. Java handles the danger at *runtime* — see the companion question on `ArrayStoreException`. ## Contrast with generics Generics deliberately did **not** repeat this mistake: `List<String>` is **not** a subtype of `List<Object>`. Generics are **invariant**. This is checked entirely at compile time, so the unsafe write is rejected before the program ever runs.

  • If arrays are covariant and that's unsafe, why didn't Java just make them invariant like generics?
    Generics didn't exist in Java 1.0, and covariant arrays were the only way to write reusable library methods like Arrays.sort(Object[]) over arbitrary reference-element arrays. The runtime ArrayStoreException check was the price for that flexibility.
  • Are arrays of primitives covariant too?
    No. Covariance only applies to reference-type element arrays. int[] and long[] have no subtype relationship; primitive types have no subtyping among themselves, so int[] is not assignable to anything but int[].

saying these in an interview costs you the question

  • Saying List<String> is a subtype of List<Object> (generics are invariant, not covariant)
  • Claiming covariance is fully type-safe (it is not safe for writes)
  • Confusing covariance with autoboxing or with widening conversions

context