skip to content

Companion & Top-Level Access From Java

From Java, companion members live on a Companion field unless annotated, top-level functions live on a FileKt facade, and const vals become static finals. Knowing these three mappings answers most 'how do I call this from Java' questions.

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

questions

6

You have a Kotlin class with a companion object containing a function create(). How do you call that function from Java, and why isn't the call simply Foo.create()?

level: juniorimportance: must knowfreq 70%

answer

  1. Default: Foo.Companion.create()
  2. Companion = static field + nested class
  3. @JvmStatic adds a real static method
  4. Foo.create() fails without @JvmStatic
  5. Named companion renames Companion

basics

~10 s

From Java you call Foo.Companion.create(). Kotlin puts companion members on a nested object named Companion, so Java must go through that object rather than calling the method straight on the class.

solid answer

~40 s

A Kotlin companion object compiles to a real nested class/instance exposed to Java as a static field named Companion on the outer class. So `companion object { fun create() = ... }` is reached from Java as `Foo.Companion.create()`. Java does NOT see `Foo.create()` by default, because Kotlin's companion is an object instance, not a set of Java static methods. To make `Foo.create()` work, annotate the companion function with `@JvmStatic`, which generates a genuine static method on `Foo` (the Companion delegate stays too). For properties, `@JvmStatic` on the val/getter or `@JvmField`/`const` changes the surfaced shape. Without those annotations, the Companion indirection is mandatory from Java.

code

kotlin · 10 lines
kotlin
class Foo {
    companion object {
        @JvmStatic fun create(): Foo = Foo()
        fun build(): Foo = Foo()
    }
}
// Java:
// Foo.create();           // OK (@JvmStatic)
// Foo.Companion.build();  // OK (default path)
// Foo.build();            // does NOT compile

go deeper

for a junior

Knows the default Java path is Foo.Companion.create() and that @JvmStatic enables Foo.create().

for a middle

Explains the generated Foo$Companion class + static Companion field and that @JvmStatic adds a delegate without removing the field.

for a senior

Discusses API-design implications: annotate factory/constants for clean Java APIs, and the named-companion option for clarity.

for a principal

Sets a team convention for which companion members get @JvmStatic to keep a stable, idiomatic Java surface across a multi-language codebase.

## What a companion object is In Kotlin a class can have ONE `companion object` — a singleton tied to the class, used for factory functions, constants, and other 'class-level' members. Kotlin lets you write `Foo.create()` from Kotlin because the compiler resolves the companion implicitly. ## How it looks to Java The companion compiles to a real nested type. The JVM sees: - a nested class `Foo$Companion` - a `public static final Foo$Companion Companion` field on `Foo` So from Java the default access path is: ```java Foo.Companion.create(); ``` Java can't write `Foo.create()` because there is no static `create` method on `Foo` — the method lives on the `Companion` instance. ## Making it a true static: @JvmStatic Annotating the companion function makes the compiler ALSO emit a static method on the outer class: ```kotlin class Foo { companion object { @JvmStatic fun create(): Foo = Foo() fun build(): Foo = Foo() // no annotation } } ``` From Java: ```java Foo.create(); // works — @JvmStatic Foo.Companion.create(); // also still works Foo.Companion.build(); // only path for build() Foo.build(); // COMPILE ERROR ``` ## Key terms - **companion object**: the per-class singleton holding class-level members. - **`@JvmStatic`**: annotation that tells the Kotlin compiler to additionally generate a JVM `static` method/accessor, so Java can call it without the `Companion` hop. - **`Companion`**: the auto-generated name of the static field/nested class (you can rename it: `companion object Named { }` → `Foo.Named`). From Kotlin nothing changes — `Foo.create()` works either way. The annotations only affect the Java-facing surface.

  • Can you change the 'Companion' name Java sees?
    Yes — give the companion a name: `companion object Factory { }` makes Java write `Foo.Factory.create()`.
  • Does @JvmStatic remove the Companion field?
    No. The Companion field/instance still exists; @JvmStatic adds a static delegate in addition, so both paths compile.

The companion is like a receptionist named 'Companion' standing at the company's front desk — Java must ask the receptionist; @JvmStatic gives the company its own direct phone line.

saying these in an interview costs you the question

  • Claiming Java can call Foo.create() with no annotation
  • Thinking companion members are Java statics by default
  • Confusing companion object with a Java static nested class with static methods
  • Saying @JvmStatic is required for Kotlin callers too

context

open as a page

A Kotlin file Utils.kt declares a top-level function fun greet(): String. How does Java call it, and where does that method actually live on the JVM?

level: juniorimportance: must knowfreq 65%

basics

~10 s

Kotlin puts top-level functions into an auto-generated class named after the file plus 'Kt'. So Utils.kt's greet() is called from Java as UtilsKt.greet().

open as a page

Contrast how a companion `const val MAX_RETRIES = 3`, a plain companion `val name = "x"`, and a companion `@JvmField val cache = ...` each surface to Java.

level: middleimportance: should knowfreq 55%

basics

~10 s

const val becomes a public static final you read directly (Foo.MAX_RETRIES). A plain val needs Foo.Companion.getName(). @JvmField exposes the field directly as Foo.cache, skipping the getter.

open as a page

Explain exactly what @JvmStatic does when placed on a companion member, what it does NOT change, and how a named companion object affects the Java access path.

level: middleimportance: should knowfreq 50%

basics

~20 s

@JvmStatic makes Kotlin emit an extra real static method on the outer class, so Java skips Companion. It doesn't remove the Companion object. Naming the companion changes 'Companion' to that name in the access path.

open as a page

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%

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.

open as a page

Walk through the exact Java-visible members generated for a companion containing a private const val, a public const val, and a @JvmStatic var. What surprises Java callers here?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

A private const is invisible to Java. A public const becomes a static final field read directly (Foo.X). A @JvmStatic var generates static getX()/setX() on the class. The surprise: const is inlined, so changing it can leave stale values in compiled Java.

open as a page