Explain @JvmStatic on companion members: what it generates, its interaction with @JvmField/const, and the interop trade-offs.
answer
- @JvmStatic = extra static bridge on the outer class
- Keeps the companion instance member too
- Property -> static getter/setter; @JvmField -> static field
- const val is already static; @JvmStatic redundant/disallowed
- Removing @JvmStatic breaks Java binary callers
basics
~10 s@JvmStatic tells the compiler to also emit a real static method or accessor on the outer class, so Java can call MyClass.foo() instead of MyClass.Companion.foo(). The companion still exists underneath.
solid answer
~40 sOn a companion function or property, @JvmStatic generates a true JVM static member on the enclosing class that forwards to the companion's instance member; the companion instance method/getter is kept, so both call forms work from Java. For properties, @JvmStatic produces static getter/setter; for plain fields you instead use @JvmField (no accessors) or const val (a static final inlined constant — already static, so @JvmStatic is redundant and disallowed on const). @JvmStatic only affects JVM/Java-facing output; Kotlin call sites are unchanged. Trade-offs: it improves Java ergonomics and avoids the Companion indirection, but adds extra synthesized members (slightly larger bytecode) and means removing it later is a source-compatible but Java-binary-affecting change. It cannot be applied to companions of interfaces' members the same way, and only valid inside objects/companion objects.
code
kotlin · 11 linesclass Mathx {
companion object {
@JvmStatic fun square(x: Int) = x * x
const val E = 2.718281828
}
}
// Java:
// Mathx.square(3); // works because of @JvmStatic
// double e = Mathx.E; // const val -> static final
// Mathx.Companion.square(3) // also still worksgo deeper
Knows @JvmStatic helps Java call companion members without 'Companion'.
Explains it generates a static bridge while keeping the companion member.
Distinguishes function/property/field cases, const redundancy, and interop trade-offs.
Treats @JvmStatic as binary contract; chooses companion vs top-level vs const for public API stability.
## What @JvmStatic does Placed on a member of a **companion object** (or any `object`), `@JvmStatic` directs the Kotlin compiler to emit an **additional real static** member on the **enclosing class** that delegates to the companion's instance member. ```kotlin class Clock { companion object { @JvmStatic fun now(): Long = System.currentTimeMillis() } } ``` Generated (conceptually): - `Clock$Companion.now()` — the instance method (unchanged), - `Clock.now()` — a static method that calls `Companion.now()`. So from **Java** both `Clock.now()` and `Clock.Companion.now()` work; without `@JvmStatic`, only the latter does. **Kotlin call sites are identical either way** (`Clock.now()`). ## Functions vs properties vs fields - **Function:** generates a static method bridge. - **Property:** generates static **getter** (and setter if `var`). The backing field stays in the companion unless you also add `@JvmField`. - **`@JvmField val x`:** exposes a **public static field** with no accessors — use when Java should read the field directly. You do not combine it with `@JvmStatic` (it already targets the field as static). - **`const val`:** a compile-time constant of a primitive or `String`; it is already `public static final` and **inlined** at call sites. `@JvmStatic` is **redundant/not allowed** on `const`. ```kotlin class Limits { companion object { const val MAX = 100 // static final, inlined @JvmField val defaults = intArrayOf(1, 2) // public static field @JvmStatic var verbose = false // static getter+setter for Java } } ``` ## Interop and design trade-offs - **Pro:** cleaner Java API (`MyClass.foo()`), no `Companion` noise, natural for libraries with mixed Kotlin/Java consumers. - **Con:** extra synthesized members (marginal bytecode/size); two ways to reach the same logic; the forwarding indirection (negligible, often inlined by JIT). - **Compatibility:** adding `@JvmStatic` is backward compatible; **removing** it breaks Java callers that used the static form. For published libraries, treat it as part of the binary contract. - **Scope:** only valid in `object`/`companion object` members. An interface companion can use it on the JVM (target 1.8+) for default-like statics, but plain abstract members cannot. ## Decision guide Use `@JvmStatic` when Java/Kotlin-mixed consumers call the member often; use `const`/`@JvmField` for constants and exposed fields; if the API is Kotlin-only, skip all of these and just rely on the companion. If no class association is needed, prefer **top-level** declarations, which compile to statics on a `*Kt` file class.
- Can you put @JvmStatic on a 'const val'?No. const val is already a static final field, so @JvmStatic is redundant and not allowed there.
- After adding @JvmStatic, does MyClass.Companion.foo() still work from Java?Yes. The companion instance member remains; @JvmStatic only adds a static forwarder, it does not remove anything.
- If a helper needs no class association, what is often simpler than a companion?A top-level function/property, which compiles to a static on the file's generated *Kt class.
saying these in an interview costs you the question
- Saying @JvmStatic moves the member out of the companion (it adds, not moves)
- Combining @JvmStatic with const val
- Claiming @JvmStatic changes Kotlin call syntax
- Ignoring that removing @JvmStatic is a Java binary-breaking change
- Confusing static getter generation with @JvmField direct field exposure