skip to content

Variance vs Java Wildcards & PECS

Kotlin's in and out map onto Java's ? super and ? extends, and PECS is the same rule stated from the other end. Knowing how the compiler emits wildcards in bytecode matters the moment your Kotlin API is consumed from Java.

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

questions

5

How do Kotlin's `out` and `in` variance modifiers map onto Java's `? extends` and `? super` wildcards, and how does this relate to the PECS rule?

level: juniorimportance: must knowfreq 70%

answer

  1. out = producer = ? extends
  2. in = consumer = ? super
  3. PECS = Producer Extends, Consumer Super
  4. covariant reads, contravariant writes
  5. copy(from: out, to: in)

basics

~10 s

out means a type only produces values, like Java's ? extends (a producer). in means a type only consumes values, like Java's ? super (a consumer). PECS: Producer Extends, Consumer Super.

solid answer

~40 s

Kotlin uses declaration-site and use-site variance instead of Java's wildcard-only model. `out T` (covariant, producer) corresponds to Java's `? extends T`: you can read `T` out but not pass it in. `in T` (contravariant, consumer) corresponds to `? super T`: you can pass `T` in but only read `Any?` out. This is exactly Josh Bloch's PECS rule — Producer-Extends, Consumer-Super. Kotlin's `MutableList<out Number>` compiles to `List<? extends Number>`; `MutableList<in Number>` compiles to `List<? super Number>`. The key insight is that Kotlin lets you declare variance once on the class (`interface List<out E>`) so callers rarely need wildcards, whereas Java forces the wildcard at every use site.

code

kotlin · 7 lines
kotlin
fun <T> copy(from: Array<out T>, to: Array<in T>) {
    for (i in from.indices) to[i] = from[i]   // read producer, write consumer
}

val ints: Array<Int> = arrayOf(1, 2)
val anys: Array<Any> = arrayOfNulls<Any>(2)
copy(ints, anys)   // from: Array<out Int>, to: Array<in Int> — both OK

go deeper

for a junior

Recites the out=extends / in=super mapping and the PECS mnemonic with a simple example.

for a middle

Explains WHY producers are covariant and consumers contravariant, and writes a correct copy signature.

for a senior

Connects declaration-site vs use-site variance and predicts the generated Java wildcard for a given Kotlin signature.

for a principal

Discusses API design tradeoffs of where to place variance and how it affects cross-language consumers and source compatibility.

## The problem variance solves Generics are *invariant* by default: `List<String>` is NOT a subtype of `List<Any>`, even though `String` is a subtype of `Any`. Variance modifiers selectively relax this. ## Covariance — `out` / `? extends` A type is a **producer** if it only *returns* (produces) `T` and never *accepts* it as a parameter. - Kotlin: `out T` (declaration-site) or `Source<out T>` (use-site projection). - Java equivalent: `? extends T`. - Rule: if it only produces `T`, then `Producer<Cat>` can safely be used as `Producer<Animal>` (you'll only ever pull out things that are at least `Animal`). ```kotlin interface Source<out T> { fun next(): T } // producer val cats: Source<Cat> = ... val animals: Source<Animal> = cats // OK: covariant ``` In Java the same thing requires a wildcard at the use site: `Source<? extends Animal>`. ## Contravariance — `in` / `? super` A type is a **consumer** if it only *accepts* `T` as a parameter and never returns it. - Kotlin: `in T` or `Sink<in T>`. - Java equivalent: `? super T`. - Rule: `Sink<Animal>` can be used as `Sink<Cat>` — anything that consumes `Animal` can certainly consume `Cat`. ```kotlin interface Sink<in T> { fun put(item: T) } // consumer val animalSink: Sink<Animal> = ... val catSink: Sink<Cat> = animalSink // OK: contravariant ``` ## PECS — the mnemonic Josh Bloch (Effective Java) coined **PECS**: **P**roducer **E**xtends, **C**onsumer **S**uper. When you read FROM a structure, use `extends` (`out`); when you write TO it, use `super` (`in`). Kotlin's `copy` signature is the canonical example: ```kotlin fun <T> copy(from: Array<out T>, to: Array<in T>) { /* read from, write to */ } ``` `from` is a producer (`out`), `to` is a consumer (`in`). This maps directly to Java's `<T> void copy(T[] src /* ? extends T */, T[] dst /* ? super T */)`. ## Why the mapping matters Kotlin compiles `out`/`in` to real Java wildcards in bytecode, so the two languages interoperate seamlessly. Understanding the correspondence lets you predict the generated Java signature and write APIs that read naturally from both sides. | Kotlin | Java | Role | |--------|------|------| | `out T` | `? extends T` | producer (read) | | `in T` | `? super T` | consumer (write) | | `T` (invariant) | `T` | both |

  • Why can't you call `put(item: T)` on a `Source<out T>`?
    Because `out` forbids `T` in `in` (parameter) positions; allowing writes would break covariance safety, so the compiler rejects it.
  • What is `out`/`in` called formally?
    `out` is covariance, `in` is contravariance; declaring them on the class is declaration-site variance.

A vending machine is out (it only dispenses); a recycling bin is in (it only accepts).

saying these in an interview costs you the question

  • Saying `out` means 'output type' generally rather than a producer/covariant position
  • Mixing up PECS — claiming Producer Super
  • Thinking Kotlin has no equivalent to Java wildcards
  • Believing `in T` lets you read `T` back out as `T` (you only get `Any?`)

context

open as a page

What Java wildcard does Kotlin's star-projection (`List<*>`) map to, and how does it differ from a raw type?

level: middleimportance: should knowfreq 50%

basics

~10 s

List<*> maps to Java's unbounded wildcard List<?>. It means 'a list of some specific but unknown type'. Unlike a raw type List, it stays type-safe: you can read Any? but can't add elements.

open as a page

Kotlin offers both declaration-site variance and use-site projections. How do each map to Java's wildcard model, and why does Kotlin prefer declaration-site?

level: middleimportance: should knowfreq 45%

basics

~20 s

Java has only use-site wildcards (? extends/? super at each usage). Kotlin adds declaration-site variance — out/in written once on the class — and also supports use-site projections (List<out T>) that map straight to wildcards.

open as a page

When does the Kotlin compiler emit Java wildcards in bytecode for a declaration-site-variant type, and what does `@JvmSuppressWildcards` / `@JvmWildcard` do?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Kotlin auto-generates Java wildcards when an out/in type appears in a method signature, so Java callers get correct variance. @JvmSuppressWildcards removes them; @JvmWildcard forces one where Kotlin would omit it.

open as a page

When Kotlin calls a Java API that uses wildcards like `List<? extends Number>`, how does Kotlin see that type, and what pitfalls arise around platform types and variance?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Kotlin translates Java's ? extends Number into out Number and ? super Number into in Number. Java types without nullability info become platform types (Number!), so you must be careful about nulls and what you can read or write.

open as a page