Kotlin's Array<T> is invariant. What does that mean, and how does it differ from Java arrays' covariance when crossing the interop boundary?
answer
- Array<T> is invariant in Kotlin
- Java arrays covariant -> ArrayStoreException at runtime
- Invariance catches the bug at compile time
- Array<out T> = safe read-only covariance
- out = producer, in = consumer
basics
~20 sInvariant means Array<String> is NOT a subtype of Array<Any>, even though String is a subtype of Any. Java arrays are covariant (String[] is an Object[]), which can blow up at runtime; Kotlin blocks that at compile time.
solid answer
~40 sKotlin declares `Array<T>` as invariant in `T`: there is no subtyping relationship between `Array<String>` and `Array<Any>`. This prevents the classic Java array hole, where `String[]` is treated as `Object[]` (covariance), letting you store an `Integer` into a `String[]` reference and only failing at runtime with `ArrayStoreException`. In Kotlin you can't assign `Array<String>` to an `Array<Any>` variable, so that write can't happen. At the interop boundary Kotlin still sees Java arrays through this invariant lens. When you need read-only flexibility, use declaration-site `out` (e.g. a function taking `Array<out Any>`), which permits passing `Array<String>` but forbids writes through that view. Use-site variance with `out`/`in` projections gives controlled covariance/contravariance.
code
kotlin · 7 lines// Compile error in Kotlin (invariance) — prevents the Java-style hole:
val strings: Array<String> = arrayOf("a")
// val any: Array<Any> = strings // type mismatch
// Safe covariant read with out-projection:
fun dump(xs: Array<out Any>) = xs.forEach(::println)
dump(strings) // OKgo deeper
Recognizes the word invariant and that Array<String> != Array<Any>.
Contrasts Kotlin invariance with Java array covariance and names ArrayStoreException.
Explains out/in use-site projections and why mutable containers must be invariant for soundness.
Reasons about variance design trade-offs across the interop boundary and how platform-typed Java arrays are surfaced (Array<(out) T!>).
## Variance terms - **Invariant**: `Container<A>` and `Container<B>` have no subtype relationship even if `A` and `B` do. Kotlin's `Array<T>` is invariant. - **Covariant**: if `A` is a subtype of `B`, then `Container<A>` is a subtype of `Container<B>`. Java arrays are covariant. - **Contravariant**: the reverse. ## The Java array hole Java made arrays covariant: `String[]` is usable as `Object[]`. That feels convenient but is unsound for writes: ```java String[] strings = { "a" }; Object[] objects = strings; // legal: covariance objects[0] = Integer.valueOf(1); // compiles; throws ArrayStoreException at runtime ``` The JVM inserts a runtime store check and throws `ArrayStoreException`. The bug surfaces only when executed. ## Kotlin closes the hole with invariance Because `Array<T>` is **invariant**, the equivalent Kotlin code does not compile: ```kotlin val strings: Array<String> = arrayOf("a") // val objects: Array<Any> = strings // ERROR: type mismatch (invariant) ``` The unsafe assignment is rejected at compile time, so the runtime `ArrayStoreException` can't arise from this path. ## Getting controlled covariance: `out` projection When a function only **reads** from an array, you can opt into covariance safely with a use-site `out` projection (an *out-projected* type): ```kotlin fun printAll(items: Array<out Any>) { for (x in items) println(x) // reading is fine // items[0] = 1 // ERROR: cannot write through out-projection } printAll(arrayOf("a", "b")) // Array<String> accepted now ``` `out` makes the type a producer (read-only at that use site), so passing a more specific array is sound. `in` would do the contravariant (write-only/consumer) direction. ## At the Java boundary When Kotlin calls Java, it views Java arrays through Kotlin's type system, so the invariance rules still guard your Kotlin code. Note that **Java method parameters typed as Java arrays are seen with `(Mutable)` array semantics**, and Kotlin generally renders a Java `T[]` parameter as `Array<(out) T!>` — a platform-type, out-projected array — to stay practical while preserving safety on writes. ## Summary - Java arrays: covariant -> possible `ArrayStoreException` at runtime. - Kotlin `Array<T>`: invariant -> unsafe assignment caught at compile time. - Use `Array<out T>` for safe read-only covariance.
- Why are Kotlin's read-only collections (List<out E>) covariant but Array<T> isn't?List exposes no public write API so covariance is sound by design; Array is mutable (indexed set), so it must stay invariant to avoid unsafe writes — you opt into covariance per use site with out.
- Does invariance stop ArrayStoreException entirely?It stops the Kotlin-source path that would cause it; arrays obtained from Java covariant code can still throw at runtime, so the JVM check remains the last line of defense.
Java arrays are a labeled box you can relabel 'anything' and then drop a wrong item in — the explosion happens later. Kotlin glues the label on so you can't relabel and sneak in the wrong item.
saying these in an interview costs you the question
- Saying Array<String> is a subtype of Array<Any> in Kotlin
- Not knowing Java arrays are covariant / ArrayStoreException exists
- Confusing invariance with immutability
- Thinking out-projection lets you write through the array