How does a Kotlin companion object differ from Java static members at the bytecode/runtime level?
answer
- Companion -> nested Companion class + static Companion field
- Java sees MyClass.Companion.foo() by default
- @JvmStatic adds a real static bridge
- @JvmField / const val expose static fields
- Companion is an object: can hold state & implement interfaces
basics
~10 sCompanion members are not real static methods by default. They live on a hidden singleton instance, so from Java you call them through a generated 'Companion' field. Adding @JvmStatic makes them true statics.
solid answer
~40 sA companion object compiles to a nested class named Companion plus a synthetic static field 'Companion' holding the single instance. Companion functions are instance methods on that object. From Kotlin, MyClass.foo() is sugar that resolves to the companion instance. From Java you must write MyClass.Companion.foo() because there is no real static method. To generate genuine JVM statics on the enclosing class, annotate members with @JvmStatic; the compiler then emits a static bridge so Java can call MyClass.foo() directly. For fields, @JvmField exposes a companion 'val/var' as a plain static field, and 'const val' produces a compile-time-constant static. Because the companion is an object, it can hold state, implement interfaces, and be referenced reflectively — unlike Java statics.
code
kotlin · 7 linesclass Config {
companion object {
const val VERSION = 3 // static final constant
@JvmStatic fun load() = Config() // callable as Config.load() from Java
val instance by lazy { Config() } // Config.Companion.getInstance() from Java
}
}go deeper
Knows you call companion members on the class name in Kotlin.
Explains the hidden Companion singleton and that Java uses MyClass.Companion.foo().
Distinguishes @JvmStatic vs @JvmField vs const val and the generated bridges.
Weighs interop cost, binary compatibility of bridges, and companion-vs-top-level API design.
## The core difference Java `static` members belong to the class itself. Kotlin companion members belong to a **singleton object** that the compiler nests inside the class. So `MyClass.foo()` in Kotlin is not a static call at the bytecode level — it is a call routed through a hidden instance. ## What the compiler generates For: ```kotlin class Repo { companion object { fun find(id: Int): String = "row-$id" } } ``` the compiler emits: - a nested class **`Repo$Companion`** containing `find` as an **instance** method, - a synthetic **static field `Repo.Companion`** holding the one instance. Kotlin call sites (`Repo.find(1)`) are rewritten to `Repo.Companion.find(1)`. **Java callers see no static method** and must write `Repo.Companion.find(1)`. ## Making real statics for Java - **`@JvmStatic`** on a companion function/property tells the compiler to also generate a **true static method** on the enclosing class, so Java can call `Repo.find(1)`. The companion instance method still exists; `@JvmStatic` adds a static bridge that delegates to it. - **`@JvmField`** on a companion `val`/`var` exposes it as a plain **public static field** (no getter), readable as `Repo.X` from Java. - **`const val`** produces a compile-time constant inlined into callers and exposed as a `public static final` field. ```kotlin class Repo { companion object { const val TABLE = "repo" // static final, inlined @JvmField val cache = HashMap<Int, String>() // public static field @JvmStatic fun find(id: Int) = "row-$id" // real static for Java } } ``` ## Why it matters Because the companion is a genuine object, it can **implement interfaces**, **extend a class**, **hold mutable state**, and be passed around (`Repo.Companion`) — capabilities Java statics lack. The trade-off is that without `@JvmStatic`/`@JvmField`, Java interop is more verbose and you pay an indirection through the singleton. ## Common confusion - `@JvmStatic` does NOT remove the companion; it adds a static forwarder. - Top-level functions/properties (file scope) compile to statics on a `FileNameKt` class and are often a simpler alternative when you do not need class association.
- From Java, how do you call a companion function that is NOT annotated with @JvmStatic?Through the generated Companion field: MyClass.Companion.theFunction().
- Does @JvmStatic delete the companion instance method?No. It adds a static method on the enclosing class that forwards to the companion's instance method.
saying these in an interview costs you the question
- Saying companion members are real JVM statics by default
- Claiming @JvmStatic is required for Kotlin-to-Kotlin calls
- Confusing @JvmField (field) with @JvmStatic (method)
- Not knowing Java must use MyClass.Companion without @JvmStatic
- Thinking @JvmStatic removes the underlying instance method