skip to content

What do @JvmStatic, @JvmField, @JvmName, @JvmOverloads, and @JvmMultifileClass do, and when would you reach for each?

level: middleimportance: should knowfreq 60%

answer

  1. @JvmStatic: real static, skip .Companion
  2. @JvmField: public field, no getter/setter
  3. @JvmName: rename / dodge erasure signature clash
  4. @JvmOverloads: generate overloads for default args
  5. @JvmMultifileClass: merge top-level fns into one facade

basics

~20 s

These @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 s

The `@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 lines
kotlin
class 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

for a junior

Recognizes @Jvm* annotations exist to make Kotlin nicer to call from Java.

for a middle

Explains each annotation's effect on emitted bytecode and a concrete use case.

for a senior

Knows the erasure-clash motivation for @JvmName and the constraints on @JvmField.

for a principal

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)

context