What do @JvmStatic, @JvmField, @JvmName, @JvmOverloads, and @JvmMultifileClass do, and when would you reach for each?
answer
- @JvmStatic: real static, skip .Companion
- @JvmField: public field, no getter/setter
- @JvmName: rename / dodge erasure signature clash
- @JvmOverloads: generate overloads for default args
- @JvmMultifileClass: merge top-level fns into one facade
basics
~20 sThese @Jvm* annotations change the Java-facing shape of compiled Kotlin so Java can call it naturally — making members static, exposing plain fields, renaming, generating overloads for default arguments, or merging top-level functions into one class.
solid answer
~40 sThe `@Jvm*` family tunes the **bytecode Kotlin emits** to be friendlier for Java callers (it usually doesn't change anything for Kotlin callers). `@JvmStatic` on a `companion object`/`object` member emits a real `static` method so Java calls `Foo.bar()` instead of `Foo.Companion.bar()`. `@JvmField` exposes a property as a public field (no getter/setter), letting Java read `obj.x` directly and required for some frameworks/constants. `@JvmName` renames the emitted method/class — needed to dodge JVM signature clashes (e.g. functions differing only by generic erasure) or to give a nicer Java name. `@JvmOverloads` generates Java-visible overloads for each default-parameter combination so Java (which lacks default args) can omit them. `@JvmMultifileClass` (with `@file:JvmName`) merges top-level declarations from several files into one generated facade class. You apply them at the boundary where mixed-language consumption matters.
code
kotlin · 11 linesclass Service {
companion object {
@JvmField val MAX = 100 // Java: Service.MAX
@JvmStatic fun create() = Service() // Java: Service.create()
}
@JvmOverloads
fun send(msg: String, retries: Int = 3) = Unit // Java: send(m) or send(m, n)
}
@JvmName("parseInt")
fun parse(s: String): Int = s.toInt() // Java: ...Kt.parseInt("5")go deeper
Recognizes @Jvm* annotations exist to make Kotlin nicer to call from Java.
Explains each annotation's effect on emitted bytecode and a concrete use case.
Knows the erasure-clash motivation for @JvmName and the constraints on @JvmField.
Treats @Jvm* placement as part of public-API design for libraries consumed by Java teams, with conventions/linting.
## Why these exist Kotlin source features (companions, properties, default args, top-level functions) compile into bytecode shapes that are *awkward to call from Java*. The **`@Jvm*` annotations** let you tune that emitted shape so Java consumption is clean. They generally do **not** affect how Kotlin sees the code. ## `@JvmStatic` Members of a `companion object` or named `object` normally compile to instance methods on a `Companion`/`INSTANCE` holder, so Java must write `Foo.Companion.bar()`. `@JvmStatic` emits an actual **`static`** method on the enclosing class, so Java writes `Foo.bar()`. ```kotlin class Foo { companion object { @JvmStatic fun bar() = 1 // Java: Foo.bar() fun baz() = 2 // Java: Foo.Companion.baz() } } ``` ## `@JvmField` A Kotlin property compiles to a private field + getter/setter. `@JvmField` instead exposes a **public field** with no accessors, so Java does `obj.x` (not `obj.getX()`). Useful for interop with reflection-based frameworks and for `const`-like exposure (note: top-level/`object` `const val` already become static final fields). Can't be used with `open`, `override`, `private`, or delegated properties. ## `@JvmName` Renames the **emitted JVM name** of a function or file class. Two uses: (1) avoid **platform signature clashes** — e.g. `fun List<Int>.sum()` and `fun List<Long>.sum()` erase to the same JVM signature, so one needs `@JvmName`; (2) give Java a cleaner name without changing the Kotlin name. `@file:JvmName("Utils")` renames the generated `XxxKt` facade. ```kotlin @JvmName("sumOfInts") fun List<Int>.sum(): Int = ... @JvmName("sumOfLongs") fun List<Long>.sum(): Long = ... ``` ## `@JvmOverloads` Kotlin has **default arguments**; the JVM does not. Without help, Java callers must pass every argument. `@JvmOverloads` generates a set of overloaded Java methods/constructors — one per trailing-default combination — so Java can omit defaulted params. ```kotlin @JvmOverloads fun greet(name: String, greeting: String = "Hi") = "$greeting, $name" // Java sees: greet(name, greeting) AND greet(name) ``` ## `@JvmMultifileClass` Top-level functions in `Util.kt` compile to a `UtilKt` class. To gather top-level declarations from **several files** under **one** Java-facing facade class, put `@file:JvmName("Utils")` + `@file:JvmMultifileClass` at the top of each file; the compiler merges them into a single `Utils` class. ```kotlin @file:JvmName("Utils") @file:JvmMultifileClass package demo fun a() = 1 // both a() and b() (in another file) // end up as static methods on Utils ``` ## Mental model Kotlin emits a *default* Java-facing API; these annotations are the **knobs** to reshape that API for ergonomic Java consumption. Reach for them only at boundaries actually consumed from Java — they add no value for pure-Kotlin code.
- Why might two Kotlin functions need @JvmName even though their Kotlin signatures differ?Generic type erasure can collapse them to the same JVM signature (e.g. List<Int> vs List<String>); @JvmName disambiguates the emitted name.
- Do these annotations change how Kotlin callers see the code?Mostly no — they tune the Java-facing bytecode. @JvmName is an exception: it changes the emitted name, which Kotlin callers using @JvmName-renamed top-level facades may also reference.
saying these in an interview costs you the question
- Saying @JvmStatic is needed for Kotlin to call companion members
- Claiming @JvmField works on open/override/delegated properties
- Thinking @JvmOverloads adds default-arg support to the JVM itself
- Confusing @JvmName (rename) with @JvmStatic (static emission)