When a Java developer calls into a Kotlin class that has a companion object, what is the 'Companion' field they see in autocomplete, and how should they reach the companion's members from Java?
answer
- Companion -> nested class + static field
- Java: MyClass.Companion.foo()
- @JvmStatic skips .Companion
- @JvmField / const -> real static field
- Kotlin never writes .Companion
basics
~10 sKotlin turns a companion object into a static field named Companion plus a nested class. From Java you call MyClass.Companion.method(), unless the member is annotated @JvmStatic, which lets you call MyClass.method() directly.
solid answer
~40 sA Kotlin companion object is compiled to a nested static class named Companion and a public static final field MyClass.Companion holding its singleton instance. Java sees both, which clutters completion. To invoke a companion function from Java you write MyClass.Companion.foo(); plain Companion members are NOT static methods on the outer class. Annotating the companion member with @JvmStatic also emits a true static bridge so Java can call MyClass.foo() directly. A companion val with @JvmField (or const for primitives/String) exposes a real static field; otherwise Java must use the generated getter MyClass.Companion.getX(). So the Companion field is the bridge Kotlin generates to give Java a handle on the companion singleton.
code
kotlin · 11 linesclass Config {
companion object {
const val VERSION = 3 // Config.VERSION (static field)
@JvmStatic fun load() = Config() // Config.load() from Java
fun parse(s: String) = Config() // Config.Companion.parse(s) from Java
}
}
// Java:
// int v = Config.VERSION;
// Config c = Config.load();
// Config d = Config.Companion.parse("x");go deeper
Knows companion -> Companion field and that Java calls MyClass.Companion.foo().
Distinguishes @JvmStatic (static bridge) from @JvmField/const (static field) and getter access.
Explains the generated nested class + field, reflection clutter, and how serializers/DI must skip it.
Weighs API design: when to add @JvmStatic/@JvmField for clean Java ergonomics vs. leaving Kotlin-idiomatic; understands tooling implications across the codebase.
## What a companion object compiles to In Kotlin a `companion object` holds members shared by all instances of a class (the rough equivalent of Java `static`). But the JVM has no notion of a companion, so the compiler generates **synthetic artifacts** to bridge it: - A **nested class** named `MyClass$Companion` containing the companion's methods/properties as instance members. - A **public static final field** `MyClass.Companion` holding the single instance of that nested class. This is the synthetic `$`-adjacent member that shows up in Java completion and reflection and surprises people. ```kotlin class Repo { companion object { fun create(): Repo = Repo() @JvmStatic fun createStatic(): Repo = Repo() const val TAG = "repo" // real static field @JvmField val SHARED = listOf(1) // real static field val cached = mutableListOf<Int>() // getter only } } ``` ## Calling it from Java ```java Repo.Companion.create(); // plain companion fun Repo.createStatic(); // @JvmStatic emits a static bridge String t = Repo.TAG; // const -> static field List<Integer> s = Repo.SHARED; // @JvmField -> static field Repo.Companion.getCached(); // plain val -> getter on Companion ``` ## The annotations that change the picture - **`@JvmStatic`** on a companion function/property generates an additional real `static` method on the **enclosing** class, so Java skips `.Companion`. - **`@JvmField`** on a companion `val`/`var` exposes a real static field (no getter). - **`const`** (compile-time constant of a primitive or `String`) always becomes a static field. From **Kotlin** you never write `.Companion`; you write `Repo.create()` directly — the compiler resolves it. The `Companion` field only matters at the Java boundary and in reflection, where it pollutes completion. ## Why it matters Reflection over `Repo::class.java` will list a `Companion` field and a `Companion` nested class. Tooling that scans fields (serializers, DI) may need to skip it.
- Why might a JSON serializer accidentally try to serialize the Companion field?Reflection-based serializers iterate declared fields; the synthetic public static final Companion field appears and must be filtered (most respect transient/synthetic flags or are configured to skip it).
- Does @JvmStatic remove the Companion field?No. It adds a static bridge method on the enclosing class but the Companion nested class and static field still exist.
The Companion field is like a receptionist desk: Java must walk up to MyClass.Companion before it can talk to any companion member, unless @JvmStatic gives that member a direct phone line.
saying these in an interview costs you the question
- Claiming plain companion members are static on the outer class
- Saying Kotlin code must also write .Companion
- Thinking @JvmStatic deletes the Companion field
- Confusing @JvmField with @JvmStatic
- Assuming every companion val becomes a static field