skip to content

How does Kotlin's diamond resolution interoperate with Java default methods, and when would you reach for super<Type>.foo() in real design (e.g. mixin/trait-style composition) versus restructuring the hierarchy?

level: principalimportance: nice to knowfreq 15%

answer

  1. Java default methods = same diamond rule
  2. Kotlin uses super<JA>.foo(), Java uses JA.super.foo()
  3. -Xjvm-default affects producing defaults for Java
  4. Qualified super = intentional mixin composition
  5. Conflict pile-up -> use by-delegation / restructure

basics

~20 s

Java default methods behave like Kotlin interface defaults, so the same 'override and qualify' rule applies when a Kotlin class mixes them. In design, use super<Type>.foo() to deliberately combine small reusable behaviors; if conflicts pile up, it's a sign to rethink the hierarchy.

solid answer

~50 s

Kotlin treats Java interface **default methods** as concrete interface members, so a Kotlin class implementing two Java (or mixed Java/Kotlin) interfaces with the same default method hits the same diamond rule: override and qualify with super<Type>.foo(). Caveat: you cannot call a Java *super* default the same way Java does (Interface.super.foo()) — from Kotlin you use the Kotlin super<Interface>.foo() form, and Kotlin can't add default implementations consumable by older Java callers without @JvmDefault-style mechanics (compiler option jvm-default). Design-wise, qualified super shines for **trait/mixin composition**: small interfaces each contribute a default behavior (logging, validation), and a class composes them, explicitly ordering super<A>.foo(); super<B>.foo(). When the override has to juggle many conflicting supers, that's a smell — prefer delegation (by), a single linearized chain, or extracting a coordinator. Diamonds are a tool for intentional composition, not a default architecture.

go deeper

for a junior

Aware Java interfaces can have default methods that interact with Kotlin.

for a middle

Knows Kotlin uses super<Type>.foo() even for Java defaults and the syntax differs from Java.

for a senior

Discusses jvm-default interop nuances and chooses qualified super vs. delegation appropriately.

for a principal

Frames diamonds as a deliberate composition trade-off, weighs mixin composition vs. restructuring/delegation, and reasons about binary compatibility of produced defaults.

## Java interop Java 8+ interfaces have **default methods**. Kotlin sees them as ordinary concrete interface members. So: ```kotlin // Two Java interfaces JA, JB each with `default void foo()` class C : JA, JB { override fun foo() { super<JA>.foo() // Kotlin's qualified super reaches the Java default super<JB>.foo() } } ``` The **same diamond rule applies** regardless of whether the conflicting defaults come from Kotlin or Java interfaces. Note the syntax difference: Java uses `JA.super.foo()`; Kotlin uses `super<JA>.foo()`. ### The other direction (jvm-default) When Kotlin *produces* interface default methods for Java consumers, the bytecode shape depends on the `-Xjvm-default` mode (e.g. `all`). Historically Kotlin compiled interface bodies to a `DefaultImpls` class; modern modes emit real JVM default methods. This affects binary compatibility and whether downstream Java sees the defaults — relevant when designing public library APIs, though it doesn't change the in-Kotlin diamond resolution. ## When to use super<Type>.foo() in design ### Good: intentional mixin/trait composition Small, focused interfaces each add behavior; a class composes them and **explicitly sequences** their contributions: ```kotlin interface Timestamped { fun render(): String { return "[ts]" } } interface Tagged { fun render(): String { return "[tag]" } } class Log : Timestamped, Tagged { override fun render() = super<Timestamped>.render() + super<Tagged>.render() } ``` The explicitness is a feature — the reader sees exactly which behaviors combine and in what order. ### When to restructure instead - Many conflicting supers in one override -> fragile, order-dependent; consider **delegation** with `by` (interface delegation) to a chosen implementation. - The same conflict recurs across many classes -> extract a common interface that resolves it once (most-specific override) so descendants don't each repeat it. - Behavior, not just signature, must be linearized -> a hierarchy where one interface `: extends` the other and overrides establishes a single most-specific impl, eliminating the diamond. ## Mental model Kotlin trades implicit linearization for **explicit, local, statically-resolved** composition. That's powerful for deliberate mixins but signals over-coupling when overrides become super-juggling. Use `by`-delegation and hierarchy extraction as the relief valves.

  • Can you write Interface.super.foo() in Kotlin like in Java?
    No. Kotlin's form is super<Interface>.foo(); the Java JA.super.foo() syntax doesn't exist in Kotlin.
  • What does interface delegation (by) offer over a juggling override?
    It forwards the whole interface to a chosen implementation, removing per-method super calls and making the composed behavior single-sourced and clearer.
  • How can you eliminate a recurring diamond entirely?
    Have one interface extend and override the other so a single most-specific implementation exists; descendants then inherit it without conflict.

saying these in an interview costs you the question

  • Saying Java default methods don't participate in Kotlin's diamond rule
  • Using JA.super.foo() syntax in Kotlin
  • Treating heavy super-juggling as good design
  • Ignoring delegation (by) as an alternative
  • Claiming Kotlin does implicit linearization to avoid conflicts

context