skip to content

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