skip to content

Your companion object implements a Java interface (e.g. companion object : Comparator<Foo>). How does Java pass that companion instance to APIs expecting the interface, and how does that differ from calling a @JvmStatic factory?

level: seniorimportance: nice to knowfreq 30%

answer

  1. Companion is an object -> can implement interfaces
  2. Pass Foo.Companion where Comparator is expected
  3. @JvmStatic gives methods, not a passable instance
  4. Name the companion for a clean handle (Foo.Order)
  5. @JvmField val INSTANCE for an explicit handle

basics

~10 s

Java grabs the singleton via Foo.Companion and passes it where the interface is expected, e.g. Collections.sort(list, Foo.Companion). A @JvmStatic factory is a static method you invoke (Foo.create()), not an instance you hand off.

solid answer

~40 s

A companion object is a real object instance, so it can implement interfaces. When `companion object : Comparator<Foo>` exists, Java accesses the singleton through the `public static final Foo$Companion Companion` field and passes IT as the interface value: `Collections.sort(list, Foo.Companion)`. That's fundamentally different from `@JvmStatic` factory methods, which expose a static METHOD you call (`Foo.create()`) — there's no object to pass. The distinction matters for interop design: if Java needs an interface IMPLEMENTATION (Comparator, Runnable, a callback), expose the companion instance (often via the `Companion` field or a `@JvmField val INSTANCE`); if Java needs to invoke behavior, expose `@JvmStatic` methods. You can't put `@JvmStatic` on the whole companion to make it 'become' the interface — the instance is the carrier.

code

kotlin · 9 lines
kotlin
class Foo(val rank: Int) {
    companion object Order : Comparator<Foo> {
        override fun compare(a: Foo, b: Foo) = a.rank - b.rank
        @JvmStatic fun create(rank: Int) = Foo(rank)
    }
}
// Java:
// Collections.sort(list, Foo.Order);  // pass the instance
// Foo f = Foo.create(5);              // invoke the static method

go deeper

for a junior

Recognizes the companion is an object and is reached via Foo.Companion.

for a middle

Knows the companion can implement an interface and be passed as that interface value to Java.

for a senior

Clearly separates 'pass an interface instance' from '@JvmStatic method invocation' and designs the surface accordingly.

for a principal

Establishes interop patterns (named companion / @JvmField INSTANCE) so Java consumers get stable, discoverable interface handles.

## Companion as an interface implementation Because a `companion object` is an actual singleton object, it can extend classes and implement interfaces: ```kotlin class Foo(val rank: Int) { companion object : Comparator<Foo> { override fun compare(a: Foo, b: Foo): Int = a.rank - b.rank @JvmStatic fun create(rank: Int): Foo = Foo(rank) } } ``` ### Passing the instance to Java APIs The companion is reachable as the static `Companion` field, which IS a `Comparator<Foo>`: ```java List<Foo> list = ...; Collections.sort(list, Foo.Companion); // Companion implements Comparator ``` No `@JvmStatic` is needed for THIS — you're handing over the singleton object, not calling a static method. If you want a cleaner name, expose it: ```kotlin companion object Order : Comparator<Foo> { ... } // Foo.Order in Java ``` then `Collections.sort(list, Foo.Order)`. ## Contrast with @JvmStatic factories `@JvmStatic fun create(rank: Int)` produces a static METHOD: ```java Foo f = Foo.create(5); // invoking behavior ``` You cannot 'pass' `Foo.create` as an object (pre-method-reference semantics differ; Java method refs would be `Foo::create`, a functional-interface adaptation, not the companion instance). ## Decision guide for interop - **Need an interface value (Comparator, Runnable, callback)?** Expose the companion instance: via `Foo.Companion`, a named companion `Foo.Order`, or a `@JvmField val INSTANCE` for an explicit handle. - **Need to invoke a class-level operation?** Use `@JvmStatic` methods so Java writes `Foo.create()`. ## Subtleties - `@JvmStatic` cannot be applied to the companion object itself to 'flatten' the interface; the interface contract lives on the instance. - For SAM (single-abstract-method) Java interfaces, Java can also pass a lambda, but if you specifically want Foo's canonical implementation, the companion instance is the carrier. - Mixing both is common: a companion that both implements `Comparator` and exposes `@JvmStatic` factories. ## Key terms - **singleton instance**: the one object the companion represents; carries interface implementations. - **`Companion` field**: the static handle Java uses to reach that instance. - **`@JvmStatic`**: surfaces methods, not instances.

  • Does the Comparator-passing case need @JvmStatic?
    No. You pass the companion instance via the Companion (or named) field; @JvmStatic only matters for surfacing methods as statics.
  • How would you give Java a more explicit handle than 'Companion'?
    Name the companion (Foo.Order) or expose a @JvmField val INSTANCE pointing at the implementation.

The companion instance is a fully built tool you can hand someone (the Comparator); a @JvmStatic method is a button on the wall you press but can't hand over.

saying these in an interview costs you the question

  • Saying you need @JvmStatic to pass the companion as a Comparator
  • Claiming a companion can't implement interfaces
  • Confusing passing the instance with calling a static method
  • Trying to apply @JvmStatic to the whole companion object
  • Assuming Foo.create can be passed as a Comparator

context