When you expose a companion factory function to Java callers, what does the generated bytecode look like, and how do @JvmStatic and @JvmName change the call site?
answer
- default Java: Type.Companion.create(...)
- @JvmStatic emits real static → Type.create(...)
- @JvmName renames the generated JVM method
- @JvmName fixes generic signature clashes
- private ctor reached via synthetic accessor
basics
~10 sBy default Java must call the factory through a Companion field, like User.Companion.create(...). Adding @JvmStatic generates a real static method so Java can call User.create(...). @JvmName lets you rename the generated method.
solid answer
~40 sA companion factory compiles to an instance method on a nested `Companion` class plus a `public static final Companion` field on the outer class. From Kotlin you call `User.create(...)`; from Java the default is `User.Companion.create(...)`. Annotating the companion function with `@JvmStatic` makes the compiler also emit a genuine `static` method directly on `User`, so Java can call `User.create(...)`. `@JvmName("...")` renames the generated JVM method — useful to avoid signature clashes (e.g. type-erased generics) or to give Java a different name. With a `private constructor`, the constructor is emitted with a synthetic accessor so the companion can reach it; Kotlin handles this automatically, but it's why a private constructor is still callable from the companion at the bytecode level. For clean Java interop on factories, prefer `@JvmStatic`; for renaming or clash avoidance, use `@JvmName`.
code
kotlin · 11 linesclass User private constructor(val name: String) {
companion object {
@JvmStatic
@JvmName("fromName")
fun create(name: String): User = User(name)
}
}
// Kotlin: User.create("a")
// Java without @JvmStatic: User.Companion.create("a")
// Java with @JvmStatic+@JvmName: User.fromName("a")go deeper
Aware that Kotlin uses Type.create(...) and that Java interop differs.
Knows Java defaults to Type.Companion.create(...) and that @JvmStatic enables Type.create(...).
Explains the generated Companion field/class, when @JvmStatic vs @JvmName apply, and platform signature clashes.
Designs the JVM-facing factory surface deliberately, managing binary compatibility, synthetic accessors for private constructors, and erasure-driven clashes.
## Default bytecode shape For: ```kotlin class User private constructor(val name: String) { companion object { fun create(name: String) = User(name) } } ``` the compiler generates roughly: - An outer class `User` with a private constructor and a `public static final User$Companion Companion` field. - A nested class `User$Companion` whose **instance** method `create(String)` calls the private constructor (via a synthetic accessor). So from **Kotlin**: `User.create("a")` (the `Companion` is implicit). From **Java**: `User.Companion.create("a")` — you go through the `Companion` field. ## @JvmStatic Annotating the companion function: ```kotlin companion object { @JvmStatic fun create(name: String) = User(name) } ``` makes the compiler **additionally** emit a real `static User create(String)` method on the `User` class itself (the Companion instance method still exists too). Now Java calls `User.create("a")` directly — idiomatic Java factory ergonomics. `@JvmStatic` only applies to members of `object`/`companion object`. ## @JvmName `@JvmName("fromName")` changes the **name of the generated JVM method** without changing the Kotlin name: ```kotlin @JvmStatic @JvmName("fromName") fun create(name: String) = User(name) ``` Use it to: - Avoid **platform signature clashes** — e.g. two functions that differ only by generic type parameters erase to the same JVM signature; `@JvmName` disambiguates them. - Provide a more Java-friendly name while keeping the Kotlin name idiomatic. ## Private constructor at the bytecode level A `private constructor` can't be called from a separate class directly, yet the `Companion` is a separate generated class. Kotlin emits a **synthetic accessor** (or marks the constructor accessible) so the companion can invoke it. This is automatic — it's the mechanism that lets the 'private constructor + companion factory' idiom work on the JVM. ## Practical guidance - Exposing factories to Java? Add **`@JvmStatic`** so call sites read `Type.create(...)`. - Hitting a 'platform declaration clash' on generic factories? Apply **`@JvmName`**. - Pure-Kotlin code needs neither; `Type.create(...)` already works. - Remember `@JvmStatic`/`@JvmName` affect only the **JVM surface**, not Kotlin call sites.
- Does @JvmStatic remove the Companion instance method?No. The Companion class and its instance method still exist; @JvmStatic adds an extra real static method on the outer class as well.
- Why might two companion factories need @JvmName?If they differ only by generic type arguments, type erasure makes their JVM signatures identical ('platform declaration clash'); @JvmName gives them distinct JVM names.
saying these in an interview costs you the question
- Thinking Java can call Type.create(...) without @JvmStatic
- Believing @JvmStatic changes the Kotlin call site
- Assuming @JvmName affects Kotlin-visible names
- Claiming a private constructor is uncallable even by its own companion
- Confusing @JvmStatic (objects) with top-level functions' @JvmName-only file facade