skip to content

Interface Default Methods (DefaultImpls)

Interface method bodies compile either to a DefaultImpls holder class or to real JVM default methods depending on the compiler flag. Which one you get decides whether a Java class implementing your interface inherits the implementation.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

When a Kotlin interface has a method with a body (a default implementation), how is that body compiled for the JVM by default, and what is the DefaultImpls class?

level: juniorimportance: must knowfreq 55%

answer

  1. Interface body -> DefaultImpls static method
  2. Receiver passed as first arg ($this)
  3. JVM interface method stays abstract (legacy)
  4. Each impl class gets a delegating bridge
  5. -Xjvm-default switches strategy

basics

~20 s

Kotlin lets interface methods have a body. By default the compiler does not put that body on the JVM interface itself; it copies it into a hidden helper class so classes implementing the interface can reuse it.

solid answer

~40 s

A Kotlin interface can declare method bodies. Historically the JVM had no default methods, so the Kotlin compiler emits the body into a synthetic nested class named DefaultImpls (e.g. MyInterface$DefaultImpls) as a static method taking the receiver as the first argument. The interface method itself stays abstract on the JVM. Each Kotlin class implementing the interface generates a bridge method that simply delegates to MyInterface$DefaultImpls.method(this, ...). This is why, with the legacy default, a Java class implementing the Kotlin interface does NOT inherit the body — it sees an abstract method and must implement it. The behavior is controlled by the -Xjvm-default compiler flag; -Xjvm-default=all makes Kotlin emit real JVM default methods instead.

code

kotlin · 12 lines
kotlin
interface Greeter {
    fun greet(): String = "hi"
}

// Conceptually the compiler (legacy mode) emits:
// public interface Greeter { String greet(); /* abstract on JVM */
//   public static final class DefaultImpls {
//     public static String greet(Greeter $this) { return "hi"; }
//   }
// }

class En : Greeter   // synthetic: String greet() { return Greeter.DefaultImpls.greet(this); }

go deeper

for a junior

Knows interfaces can have bodies and that a hidden helper class holds them.

for a middle

Can name DefaultImpls, the static method with receiver-as-first-arg, and the bridge in implementors.

for a senior

Explains the JVM-6-history rationale and the abstract-on-JVM interop consequence for Java.

for a principal

Frames it as an ABI strategy and connects it to -Xjvm-default mode choices for library evolution.

## The feature Kotlin interfaces may contain **method bodies** (default implementations) and even default-implemented properties (without backing fields). On the source level this looks just like Java 8+ default methods. ## Why DefaultImpls exists Kotlin originally targeted **JVM 6**, which had no `default` methods on interfaces (those arrived in Java 8 bytecode). To still allow interface bodies, the compiler puts the code somewhere it is legal: a synthetic nested class. For an interface `Greeter` with `fun greet() = "hi"`, the compiler emits: - The interface `Greeter` with `greet()` left **abstract** on the JVM. - A nested class `Greeter$DefaultImpls` containing a **static** method `greet(Greeter $this)` that holds the actual body. The receiver is passed explicitly as the first parameter. Each Kotlin class that implements `Greeter` and does not override `greet` gets a generated **bridge** method whose body is `return Greeter$DefaultImpls.greet(this)`. ```kotlin interface Greeter { fun greet(): String = "hi" // body lives in Greeter$DefaultImpls } class En : Greeter // gets a synthetic greet() delegating to DefaultImpls ``` ## The interop consequence Because the JVM interface method is abstract, **Java code** implementing `Greeter` sees no inherited body and is forced to implement `greet()` itself. From Kotlin everything works as expected; the asymmetry only bites Java consumers. ## Controlling it The `-Xjvm-default` compiler flag changes the strategy: - `disable` (legacy default in older versions): DefaultImpls only. - `all`: emit **real JVM `default` methods** directly on the interface, so Java inherits the body. DefaultImpls largely disappears. - `all-compatibility`: emit real default methods **and** keep DefaultImpls for binary compatibility with already-compiled callers. Keywords to know: `-Xjvm-default`, `DefaultImpls`, synthetic static method, bridge method.

  • Why was the receiver passed explicitly as the first parameter of the DefaultImpls method?
    DefaultImpls methods are plain static methods with no implicit `this`, so the interface instance must be threaded in manually to let the body call other members on `$this`.
  • Does a Kotlin class implementing the interface ever need to do anything special to get the default?
    No. The compiler auto-generates the delegating bridge for any Kotlin implementor that doesn't override the method.

DefaultImpls is like a shared recipe card kept in a side drawer: each cook (class) copies it, but a Java cook who never opens that drawer has to write the recipe from scratch.

saying these in an interview costs you the question

  • Claiming Kotlin always compiles interface bodies to real JVM default methods
  • Saying Java automatically inherits the body in legacy mode
  • Confusing DefaultImpls with companion objects
  • Thinking the developer must write DefaultImpls by hand
  • Not knowing a compiler flag controls the behavior

context

open as a page

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%

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.

open as a page

Compare -Xjvm-default=all and -Xjvm-default=all-compatibility. What does each emit, and which would you choose when evolving a published Kotlin library?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Both make interface bodies into real JVM default methods so Java inherits them. 'all' drops the old helper class; 'all-compatibility' keeps it too, so code already compiled against the helper still links. For published libraries, use all-compatibility during migration.

open as a page

Beyond plain functions, how do Kotlin interface property accessors with default bodies and methods marked @JvmStatic in interfaces interact with the DefaultImpls / -Xjvm-default mechanism?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Interface properties with default getters follow the same rule as default methods: in legacy mode the getter body sits in the helper class; with -Xjvm-default=all it becomes a real default getter Java can inherit. The same flag governs all default members.

open as a page

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?

level: principalimportance: nice to knowfreq 12%

basics

~20 s

If 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.

open as a page