A Java class implements a Kotlin interface that provides a default method body. Why might the Java compiler force you to implement that method anyway, and how do you fix it?
answer
- Legacy = abstract on JVM -> Java must implement
- -Xjvm-default=all -> real default -> Java inherits
- all-compatibility keeps DefaultImpls for ABI
- @JvmDefault is deprecated, use the flag
- Set freeCompilerArgs / jvmDefault in Gradle
basics
~20 sIn Kotlin's legacy compilation mode the body is stored in a side class, and the interface method stays abstract for the JVM. So Java sees no inherited code and demands you write the method. Fix it by compiling the Kotlin with -Xjvm-default=all.
solid answer
~30 sBy default (legacy `-Xjvm-default=disable`), Kotlin keeps the interface method **abstract** on the JVM and stores the body in `Iface$DefaultImpls`. Java's inheritance only sees real JVM `default` methods, so a Java implementor sees an abstract method and the compiler errors unless it provides an implementation. To make Java inherit the body, compile the Kotlin module with `-Xjvm-default=all` (or `all-compatibility` when you need binary compatibility with previously compiled Kotlin callers). With `all`, the compiler emits a genuine `default` method on the interface and Java inherits it normally. Per-declaration control existed via the now-deprecated `@JvmDefault` annotation; modern Kotlin uses the module-wide flag instead.
code
kotlin · 11 lines// build.gradle.kts
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
compilerOptions {
freeCompilerArgs.add("-Xjvm-default=all")
}
}
interface Plugin {
fun name(): String
fun version(): String = "1.0" // real JVM default method now; Java inherits it
}go deeper
Recognizes that Java is being forced to implement the method but may not know the exact reason.
Explains abstract-on-JVM legacy behavior and fixes it with -Xjvm-default=all.
Distinguishes all vs all-compatibility and reasons about ABI impact on published libraries.
Plans a migration strategy for a public Kotlin library, weighing binary compatibility, deprecation of @JvmDefault, and consumer impact.
## The asymmetry From Kotlin, a default interface method 'just works' for both Kotlin and Java implementors of your reasoning — but only Kotlin implementors actually inherit the behavior in **legacy mode**. The reason is purely about **bytecode shape**: - **Legacy `-Xjvm-default=disable`**: interface method is **abstract** on the JVM; body lives in `Iface$DefaultImpls.method($this, …)`. Kotlin implementors get a generated bridge that calls DefaultImpls. **Java implementors get nothing inherited** — `javac` reports the method as not implemented (abstract class error) and forces an override. - **`-Xjvm-default=all`**: the compiler emits a real JVM `default` method on the interface. Now standard JVM interface inheritance applies, so **Java inherits the body** with no extra code. - **`-Xjvm-default=all-compatibility`**: emits the real default method **and** keeps `DefaultImpls` so already-compiled binaries that referenced DefaultImpls still link. ## Fixing the Java-implementor problem ```kotlin // Kotlin library module compiled with -Xjvm-default=all interface Plugin { fun name(): String fun version(): String = "1.0" // becomes a real JVM default method } ``` ```java // Java consumer class MyPlugin implements Plugin { @Override public String name() { return "x"; } // version() inherited — no need to override } ``` ## Historical per-method control The `@JvmDefault` annotation (Kotlin 1.2–1.3 era) toggled a single method into a real default method. It is **deprecated**; today you choose the strategy at the module level via the compiler flag, which is far simpler to reason about. ## Gradle setup Add the flag to the Kotlin compiler options, e.g. `freeCompilerArgs.add("-Xjvm-default=all")` (or, on newer toolchains, the dedicated `jvmDefault` option). Choose `all` for greenfield, `all-compatibility` if external code already compiled against your DefaultImpls ABI. ## Key terms `-Xjvm-default`, `default` (JVM keyword), `DefaultImpls`, `@JvmDefault` (deprecated), binary/ABI compatibility.
- When would you pick all-compatibility over all?When external code was already compiled against the DefaultImpls ABI; all-compatibility keeps DefaultImpls so those binaries still link while new code gets real default methods.
- Does switching a published library from disable to all break binary compatibility?It can: callers compiled against DefaultImpls may fail to link because DefaultImpls is removed under `all`. Use `all-compatibility` to bridge the transition.
saying these in an interview costs you the question
- Believing Java inherits Kotlin defaults regardless of compiler flags
- Suggesting @JvmDefault as the current recommended approach
- Not knowing all-compatibility exists for ABI safety
- Claiming the fix is a Java-side annotation
- Confusing source-level visibility with bytecode default-method presence