skip to content

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%

answer

  1. Variance = metadata only; JVM = wildcards
  2. out -> ? extends, in -> ? super in generated signatures
  3. @JvmSuppressWildcards strips, @JvmWildcard forces
  4. final classes -> no wildcard generated
  5. matters for Java interop / reflection / serialization

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.

solid answer

~40 s

Declaration-site variance lives only in Kotlin metadata; the JVM only understands wildcards. So when a covariant (`out`) type parameter appears in a method *return* or parameter, the compiler emits `? extends Bound` (and `? super` for `in`) so Java consumers see correct PECS variance. Kotlin omits the wildcard in positions where it's redundant (e.g. final classes, or to keep signatures readable), and you can override the heuristic: `@JvmSuppressWildcards` strips wildcards (giving an invariant signature, useful when Java needs an exact type), and `@JvmWildcard` forces one at a specific type position. Both can be applied at declaration, type, or even file scope. A classic need: a `List<out Foo>` parameter generates `List<? extends Foo>`, which can be awkward for some Java APIs — suppressing yields plain `List<Foo>`.

code

kotlin · 8 lines
kotlin
// default: wildcard generated
fun a(xs: List<out Number>) {}            // Java: List<? extends Number>

// suppressed: invariant signature
fun b(xs: List<@JvmSuppressWildcards Number>) {}  // Java: List<Number>

// forced even where omitted
fun c(xs: List<@JvmWildcard Number>) {}    // Java: List<? extends Number>

go deeper

for a junior

Aware that Kotlin variance becomes Java wildcards but fuzzy on the mechanics.

for a middle

Can state out->? extends / in->? super generation and that final classes skip wildcards.

for a senior

Explains the two override annotations precisely and a real interop scenario (reflection/serialization) requiring them.

for a principal

Weighs binary-compatibility and API-design implications of emitting vs suppressing wildcards across a public Java-facing surface.

## Why translation is needed The JVM has no concept of declaration-site variance. `interface Source<out T>` carries its variance in Kotlin `@Metadata`, but a Java compiler only reads the erased + wildcard signatures. To preserve PECS for Java callers, the Kotlin compiler **injects wildcards** when a variant type parameter is *used* in a signature. ## Default wildcard generation rules - An `out T` parameter type generates `? extends Bound`. - An `in T` parameter type generates `? super Bound`. - Wildcards are generated mainly for **parameter** positions and other places where Java needs them to call the method covariantly/contravariantly. - Kotlin **omits** wildcards when they'd be redundant or harmful: e.g. when the argument type's class is `final` (no subtypes, so a wildcard adds nothing), or for return types in many cases. ```kotlin // Kotlin fun produce(list: List<out Number>) {} // Generated Java signature: // void produce(java.util.List<? extends Number> list) ``` ## `@JvmSuppressWildcards` Forces the compiler to emit the **invariant** type — no wildcard. ```kotlin fun produce(list: List<@JvmSuppressWildcards Number>) {} // Generated: void produce(List<Number> list) ``` Useful when a Java framework (reflection, serialization, some builders) needs the exact reified-looking type and a `? extends` confuses it. Can annotate a single type, a function, a class, or a whole file; it cascades to nested types. ## `@JvmWildcard` Forces a wildcard **where Kotlin would have omitted one**. ```kotlin fun produce(list: List<@JvmWildcard Number>) {} // Generated: void produce(List<? extends Number> list) ``` The two annotations are opposites and can be nested: an outer `@JvmSuppressWildcards` with an inner `@JvmWildcard` lets you control variance per type argument. ## Why you'd care - **Java interop ergonomics**: a generated `? extends T` can break Java code that expects an exact `T`. - **Reflection / annotation processors** (e.g. some DI or serialization libs) read the generic signature and misbehave on wildcards. - **Source vs binary compatibility**: changing whether a wildcard is emitted changes the public Java-visible signature, a potential binary-compat break. ## Key takeaways - Variance is Kotlin-only metadata; wildcards are the JVM representation. - The compiler translates automatically following PECS. - `@JvmSuppressWildcards` / `@JvmWildcard` are the manual overrides when the heuristic doesn't match what a Java consumer needs.

  • Why might Kotlin NOT emit a wildcard for a `final` class type argument?
    A final class has no subtypes, so `? extends Final` is equivalent to `Final`; omitting the wildcard yields a cleaner, equally-safe signature.
  • Can `@JvmSuppressWildcards` be applied to a whole file?
    Yes — as a file-level annotation (`@file:JvmSuppressWildcards`) it cascades to every declaration in the file.
  • Why do serialization/DI libraries sometimes need wildcards suppressed?
    They reflect over the generic signature expecting an exact type token; a `? extends` wildcard produces a `WildcardType` they don't resolve, causing failures.

saying these in an interview costs you the question

  • Claiming the JVM understands declaration-site variance directly
  • Saying wildcards are never generated automatically
  • Confusing the two annotations' directions (suppress vs force)
  • Asserting wildcard generation never affects binary compatibility
  • Thinking `@JvmSuppressWildcards` changes runtime behavior rather than the signature

context