skip to content

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?

level: middleimportance: must knowfreq 50%

answer

  1. Legacy = abstract on JVM -> Java must implement
  2. -Xjvm-default=all -> real default -> Java inherits
  3. all-compatibility keeps DefaultImpls for ABI
  4. @JvmDefault is deprecated, use the flag
  5. Set freeCompilerArgs / jvmDefault in Gradle

basics

~20 s

In 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 s

By 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
kotlin
// 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

for a junior

Recognizes that Java is being forced to implement the method but may not know the exact reason.

for a middle

Explains abstract-on-JVM legacy behavior and fixes it with -Xjvm-default=all.

for a senior

Distinguishes all vs all-compatibility and reasons about ABI impact on published libraries.

for a principal

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

context