What does the -Xjvm-default compiler flag do, and what are the practical differences between its modes (e.g. all, all-compatibility)?
answer
- Controls DefaultImpls vs real JVM default methods
- all = real defaults, no DefaultImpls, binary-incompatible
- all-compatibility = real defaults + DefaultImpls bridges
- Kotlin 2.2: jvmDefault ENABLE / NO_COMPATIBILITY / DISABLE
- Libraries -> all-compatibility; apps -> all
basics
~10 sIt controls whether Kotlin interface methods with bodies become real Java default methods in the bytecode, instead of being copied into a synthetic DefaultImpls class. The modes trade binary compatibility against cleaner Java interop.
solid answer
~40 s-Xjvm-default decides how Kotlin compiles **interface methods that have default bodies**. Historically Kotlin emitted the body into a synthetic `DefaultImpls` nested class so older JVMs (pre-8 default-method support) and binary compatibility were preserved; Java callers couldn't see it as a real default method. Mode **all** emits a genuine JVM `default` method and drops `DefaultImpls` — cleanest Java interop, but breaks binary compatibility with code compiled the old way. Mode **all-compatibility** emits the real default method *and* keeps `DefaultImpls` bridges, so already-compiled subclasses still link — the migration-friendly choice for libraries. In Kotlin 2.2 this is exposed via the stable `-jvm-default` / `compilerOptions.jvmDefault` with values `ENABLE` (≈ all-compatibility), `NO_COMPATIBILITY` (≈ all), and `DISABLE`. Pick all-compatibility for published libraries, all for app code or when you don't owe binary compatibility.
code
kotlin · 11 linesinterface Repository {
fun describe(): String = "repo" // default body
}
// With jvmDefault = NO_COMPATIBILITY (all): describe() becomes a real
// JVM default method; no Repository$DefaultImpls is generated, so a Java
// class implementing Repository inherits describe() for free.
//
// With jvmDefault = ENABLE (all-compatibility): the real default method
// is emitted AND DefaultImpls is kept so previously-compiled Kotlin
// subclasses still link.go deeper
May only know interfaces can have default bodies; likely unaware of the flag — acceptable to admit.
Knows the flag relates to default methods vs DefaultImpls and improves Java interop.
Explains all vs all-compatibility, the binary-compatibility tradeoff, and picks the right mode for libraries vs apps.
Plans a migration across a library ecosystem, using per-interface annotations and the Kotlin 2.2 stable jvmDefault to avoid breaking downstream consumers.
## The problem it solves Kotlin lets interfaces have method bodies: ```kotlin interface Greeter { fun greet(): String = "hello" // default body } ``` The JVM has supported real `default` interface methods since Java 8 — but Kotlin originally compiled the body into a **synthetic nested class** named `DefaultImpls` (e.g. `Greeter$DefaultImpls.greet(...)`), and made each implementing class call into it. This preserved binary compatibility and old-JVM support, but a **Java** class implementing `Greeter` did *not* inherit the body — it was forced to implement `greet()` itself, because Java only sees the abstract method. ## What -Xjvm-default changes The flag controls whether the body is emitted as a **genuine JVM `default` method** on the interface (visible to Java) or kept in `DefaultImpls`. Legacy mode names (`-Xjvm-default=...`): - **`disable`** — old behavior: only `DefaultImpls`, no real default methods. - **`all`** — emit real JVM default methods, **no** `DefaultImpls`. Best Java interop, but **binary-incompatible** with artifacts compiled the old way (callers that expected `DefaultImpls` break). - **`all-compatibility`** — emit real default methods **and** keep `DefaultImpls` as compatibility bridges. Java sees defaults; old compiled Kotlin still links. Slightly larger bytecode; the migration-safe choice for **published libraries**. ## Kotlin 2.2 stable option Kotlin 2.2 promoted this to a stable option, `-jvm-default` / `compilerOptions.jvmDefault`, with enum values: - **`ENABLE`** ≈ all-compatibility (default going forward), - **`NO_COMPATIBILITY`** ≈ all, - **`DISABLE`** ≈ disable. ```kotlin kotlin { compilerOptions { jvmDefault.set(org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode.ENABLE) } } ``` ## Choosing a mode - **Library you publish** → `all-compatibility` / `ENABLE`: keeps already-compiled consumers linking while giving Java callers real defaults. - **Leaf application module** → `all` / `NO_COMPATIBILITY` is fine; you control all callers, so cleaner bytecode and no `DefaultImpls` clutter. - **`@JvmDefaultWithoutCompatibility`** / `@JvmDefaultWithCompatibility` annotations let you tune per-interface when migrating. The net effect is purely about **JVM interface-default-method bytecode shape** and **binary compatibility**, not source semantics — Kotlin code behaves the same either way.
- Why is mode 'all' risky for a published library?Dropping DefaultImpls is a binary-incompatible change: consumers compiled against the old shape expect the DefaultImpls bridge and can fail to link/run after the upgrade.
- Does -Xjvm-default change how the code behaves when called from Kotlin?No. Kotlin source semantics are identical; the flag only changes the emitted JVM bytecode shape and Java interop / binary compatibility.
all-compatibility is like keeping the old phone-line adapter plugged in while you switch everyone to fiber — both routes still work during the migration.
saying these in an interview costs you the question
- Thinking it changes Kotlin source behavior, not just bytecode
- Recommending 'all' for a published library without warning about binary compatibility
- Not knowing DefaultImpls is the legacy mechanism
- Confusing it with jvmTarget (it's independent of bytecode version, only needs JVM 8+ default-method support)
- Unaware Kotlin 2.2 made jvmDefault a stable option