skip to content

SAM Conversions

You can pass a Kotlin lambda anywhere a Java single-abstract-method interface is expected, with explicit constructors like Runnable { } when inference needs help. The catch worth knowing is that Kotlin interfaces need the fun modifier to get the same treatment.

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

questions

5

What is a SAM conversion in Kotlin, and how does it let you pass a lambda where a Java interface like Runnable or Comparator is expected?

level: juniorimportance: must knowfreq 70%

answer

  1. SAM = Single Abstract Method
  2. Lambda -> anonymous interface instance
  3. Runnable, Comparator, Callable
  4. Java interfaces only by default
  5. Compile-time sugar, no reflection

basics

~20 s

A SAM conversion lets you pass a short Kotlin lambda where Java expects an interface with exactly one method, like Runnable or Comparator. Kotlin turns the lambda into that interface automatically, so you skip the boilerplate anonymous class.

solid answer

~40 s

SAM stands for Single Abstract Method. When a Java interface has exactly one abstract method (Runnable.run, Comparator.compare, Callable.call), Kotlin lets you pass a lambda directly instead of writing an anonymous object. The compiler wraps the lambda in an instance of that interface, mapping the lambda parameters to the method's parameters and its body to the method body. For example, Thread { println("hi") } passes a lambda where a Runnable is wanted, and listOf(3,1,2).sortedWith { a, b -> a - b } passes a Comparator. This only works for Java SAM interfaces by default; Kotlin's own interfaces need the fun interface modifier. The conversion is purely syntactic sugar over an anonymous implementation.

code

kotlin · 8 lines
kotlin
// Java expects a Runnable (one method: run)
val t = Thread { println("running") }
t.start()

// Java expects a Comparator<String> (one method: compare)
val names = listOf("bob", "al", "cy")
val byLength = names.sortedWith { a, b -> a.length - b.length }
println(byLength) // [al, cy, bob]

go deeper

for a junior

Knows SAM = single-abstract-method and can pass a lambda to Thread{} or sortedWith{}.

for a middle

Explains the matching of lambda params to method params and that it's Java-interface-only by default.

for a senior

Notes the generated anonymous class / invokedynamic and the fun interface boundary for Kotlin.

for a principal

Frames SAM conversion as part of a deliberate interop ergonomics policy and its compile-time, zero-reflection guarantees.

## What SAM means **SAM = Single Abstract Method.** It refers to any interface that declares exactly one abstract (unimplemented) method. Classic Java examples: - `Runnable` — one method `void run()` - `Comparator<T>` — one method `int compare(T a, T b)` - `Callable<V>` — one method `V call()` ## The problem SAM conversion solves Without it, to satisfy a Java method that wants a `Runnable` you'd write a full anonymous implementation: ```kotlin Thread(object : Runnable { override fun run() { println("work") } }) ``` A **SAM conversion** lets the Kotlin compiler accept a lambda in that position and silently build the anonymous object for you: ```kotlin Thread { println("work") } // lambda becomes a Runnable ``` The lambda's parameters map onto the single method's parameters and the lambda body becomes the method body. For `Comparator<Int>`: ```kotlin val sorted = listOf(3, 1, 2).sortedWith { a, b -> a - b } // { a, b -> a - b } is converted to Comparator.compare(a, b) ``` ## Where it applies SAM conversion fires when: - The expected type is a **Java interface** with exactly one abstract method, AND - You supply a function value (a lambda or a `::method` reference) whose signature matches. It does **not** fire for Java *classes* (even abstract ones with a single method) — only interfaces. By default it also does **not** fire for **Kotlin** interfaces; you need the `fun interface` modifier for those (a separate feature called a *functional interface*). ## Mechanism, not magic The conversion is compile-time sugar. The generated bytecode is an anonymous class (or an `invokedynamic` lambda metafactory call) implementing the interface — the same thing you'd write by hand. There's no runtime reflection involved. ## Key takeaways - Saves boilerplate when calling Java APIs that take functional interfaces. - Only one abstract method allowed (default methods don't count against it). - Java interfaces work out of the box; Kotlin interfaces need `fun interface`.

  • Does SAM conversion work for an interface with two abstract methods?
    No. There must be exactly one abstract method. Extra abstract methods disqualify it; default/static methods don't count.
  • Can you pass a method reference instead of a lambda for a SAM conversion?
    Yes. A bound or unbound reference like ::doWork or obj::handle converts just like a lambda as long as its signature matches the single method.

It's like handing a one-line note where a form letter was expected — the system fills out the formal wrapper around your one line for you.

saying these in an interview costs you the question

  • Thinking SAM conversion works for any class with one method (it's interfaces only)
  • Believing it works automatically for Kotlin interfaces without fun interface
  • Claiming it uses runtime reflection rather than compile-time sugar
  • Confusing SAM (one abstract method) with 'any interface'

context

open as a page

Why can't you SAM-convert a lambda for a Kotlin interface by default, and how do you enable it?

level: middleimportance: must knowfreq 60%

basics

~10 s

By default Kotlin only auto-converts lambdas for Java single-method interfaces. For a Kotlin interface you mark it 'fun interface'. Without that, you must write a full object expression implementing the interface.

open as a page

What is a SAM constructor (e.g. `Runnable { ... }`), and when do you need to use it explicitly instead of a bare lambda?

level: middleimportance: should knowfreq 45%

basics

~20 s

A SAM constructor wraps a lambda into a specific interface by writing the interface name before the lambda, like Runnable { ... }. You use it when the target type is ambiguous — for overloads, generics, or storing the value in a variable.

open as a page

What surprising behaviors can SAM conversions cause around object identity, and how do you handle a Java method that takes a registered then-unregistered listener?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Each SAM-converted lambda may become a different object instance, so you can't reliably remove a listener by passing the 'same' lambda again. Capture the converted instance in a variable and reuse that exact reference for both add and remove.

open as a page

Name situations where SAM conversion does NOT apply, and explain how a Kotlin function type passed to Java differs from a SAM-converted lambda.

level: seniorimportance: should knowfreq 30%

basics

~20 s

SAM conversion needs a Java interface with exactly one abstract method as the target. It won't fire for abstract classes, multi-method interfaces, plain Kotlin interfaces, or when the parameter is already a Kotlin function type. A function type passed to Java surfaces as a FunctionN object, not your interface.

open as a page