With -Xjvm-default=all, how does Kotlin handle a class inheriting conflicting default methods from two interfaces, and how do super-interface calls compile compared with legacy DefaultImpls mode?
answer
- Conflict -> must override + super<Iface>.foo()
- all: super-call = invokespecial on interface
- legacy: super-call = DefaultImpls static call
- Cross-mode link errors: NoSuchMethod / AbstractMethod
- Keep -Xjvm-default stable across an ABI
basics
~20 sIf two interfaces give the same method a body, Kotlin makes the class override it and pick one with super<Iface>.method(). Under -Xjvm-default=all this maps to a real JVM invokespecial on the interface; in legacy mode it calls the helper class instead.
solid answer
~40 sWhen a class inherits two default implementations of the same signature (the diamond problem), Kotlin **requires** an explicit override and you disambiguate with `super<InterfaceName>.method()`. The bytecode differs by mode: under `-Xjvm-default=all`, the methods are real JVM `default` methods, so `super<A>.foo()` compiles to an `invokespecial` targeting `A.foo` — the standard JVM default-method super invocation. In legacy `DefaultImpls` mode, the interface method is abstract on the JVM, so `super<A>.foo()` compiles to a static call `A$DefaultImpls.foo(this)`. Mixing modes across modules is the dangerous part: a class compiled in legacy mode that super-calls into an interface later recompiled with `all` (DefaultImpls removed) can fail to link. This is why crossing the boundary is a binary-compatibility concern and why `all-compatibility` exists.
code
kotlin · 9 linesinterface A { fun foo() = "A" }
interface B { fun foo() = "B" }
class C : A, B {
// Diamond: compiler forces an override.
// all mode -> invokespecial A.foo / B.foo
// legacy -> A$DefaultImpls.foo(this) / B$DefaultImpls.foo(this)
override fun foo() = super<A>.foo() + super<B>.foo()
}go deeper
Knows a conflict needs an override but likely not the syntax or bytecode details.
Can write super<A>.foo() to resolve a diamond but may not know the bytecode differences.
Explains invokespecial vs DefaultImpls static-call emission per mode.
Reasons about cross-module ABI hazards (NoSuchMethodError/AbstractMethodError), migration strategy, and treats the flag as a release contract.
## The diamond / conflict rule Kotlin's source-level rule is mode-independent: if a class inherits **two** default bodies for the same signature, the compiler forces you to override and resolve explicitly. ```kotlin interface A { fun foo() = "A" } interface B { fun foo() = "B" } class C : A, B { override fun foo() = super<A>.foo() + super<B>.foo() // explicit disambiguation } ``` The operator here is the **qualified super** `super<TypeName>.member()`. ## How the super-call compiles - **`-Xjvm-default=all`**: `foo` is a real JVM `default` method. `super<A>.foo()` becomes an `invokespecial A.foo` — the JVM's native mechanism for invoking a specific super-interface default. This is exactly how Java's `A.super.foo()` works. - **Legacy `DefaultImpls`**: `foo` is abstract on the JVM, so the super-call cannot use `invokespecial` on the interface. The compiler instead emits a **static** call `A$DefaultImpls.foo(this)`. ## Cross-module / cross-mode hazards The placement difference is an **ABI** detail, so mismatches bite at link time: - A class compiled in **legacy** mode super-calling interface `A` emits a reference to `A$DefaultImpls.foo`. If `A` is **recompiled with `all`**, DefaultImpls disappears -> `NoSuchMethodError` at runtime. - Conversely, a class compiled against an `all` interface uses `invokespecial`, which an old legacy interface jar (no real default method) cannot satisfy -> `AbstractMethodError`. - `all-compatibility` mitigates the first case by keeping DefaultImpls delegators. ## Design guidance for library authors - Pick the mode at the **module** level and keep it stable across a published ABI; document it. - When migrating a public interface from legacy to `all`, prefer `all-compatibility` and bump the **major** version. - Be aware that adding a new default method to a published interface is source-compatible but can still surprise Java implementors that already had a same-signature method. - ArchUnit/Modulith-style boundary tests do not catch ABI drift — treat `-Xjvm-default` as part of your release contract. ## Keywords `super<Type>.method()` (qualified super), diamond problem, `invokespecial`, JVM `default` method, `DefaultImpls`, `NoSuchMethodError`, `AbstractMethodError`, ABI.
- What runtime error appears when a class compiled against an `all`-mode interface runs against an old legacy jar of that interface?An AbstractMethodError: the call site uses invokespecial expecting a real default method, but the legacy interface only has an abstract method and DefaultImpls.
- Does the source-level diamond resolution rule change between modes?No. The requirement to override and use super<Type>.foo() is identical; only the emitted bytecode for the super-call differs.
saying these in an interview costs you the question
- Saying Kotlin auto-picks one default in a diamond without an explicit override
- Using Java's A.super.foo() syntax in Kotlin instead of super<A>.foo()
- Ignoring cross-mode linkage errors when evolving interfaces
- Claiming super-calls compile identically in all and legacy modes
- Treating -Xjvm-default as a non-ABI, purely-internal detail