Where is @JvmStatic NOT allowed, and what subtle issue arises with @JvmStatic on a companion inside an interface?
answer
- Only on companion/named-object members
- Rejected: top-level, normal class member, const val
- Interface companion needs JVM target 8+ (bytecode 52)
- Static forwarder = no dynamic dispatch
- Pairs with @JvmName/@Throws, not @JvmField
basics
~10 sYou can only use @JvmStatic on companion or named-object members. It is rejected on top-level functions, regular class members, and const vals. On interface companions it works but historically needed a newer JVM target.
solid answer
~50 s`@JvmStatic` is valid only on members of a **companion object** or a **named `object`**. It is a compile error on: top-level functions/properties (already compiled to static members of a file class), members of a regular (non-object) class, `const val` (already a static final field), and on the companion object declaration itself rather than its members. The interesting edge case is a companion **inside an interface**: emitting a real static method on an interface requires `static` methods in interfaces, which the JVM only supports from bytecode version 52 (Java 8). With modern Kotlin defaulting to Java 8+ targets this works fine, and the static forwarder is placed on the interface. You should also know that the forwarder is not `open`/virtual — `@JvmStatic` members cannot be overridden polymorphically through the static entry point, since static methods are not subject to dynamic dispatch.
go deeper
May only know it works on companion objects, without the precise rejection list.
Lists the main invalid placements (top-level, const val, normal class).
Adds the interface-companion JVM-target nuance and the no-dynamic-dispatch property of the static forwarder.
Turns the rules into a consistent interop API policy and reasons about cross-target/bytecode-version compatibility.
## Where `@JvmStatic` is rejected The annotation is **only** meaningful and allowed on members declared inside a `companion object` or a named `object`. The compiler reports an error for: - **Top-level functions/properties** — these already compile to `static` members of a synthetic file class (e.g. `UtilKt`), so a static-making annotation is meaningless. (Renaming that file class is `@file:JvmName`, a different concern.) - **Members of a normal class** — instance members can't be static. - **`const val`** — already a `static final` field with an inlined constant; nothing to convert. - **The companion/object declaration itself** — you annotate the *members*, not the object. ## Interface companion edge case ```kotlin interface Codec { companion object { @JvmStatic fun default(): Codec = Utf8Codec } } ``` Generating `Codec.default()` requires a **static method on an interface**, which is only legal in JVM bytecode **version 52 (Java 8) and later**. With older `-jvm-target 1.6`, this was disallowed. Modern Kotlin defaults to Java 8+ (commonly 17/21), so the static forwarder is emitted directly on the interface and Java can call `Codec.default()`. ## No virtual dispatch A `@JvmStatic` forwarder is a **static** method. Static methods are resolved at compile time on the class named, not via the receiver's runtime type — there is **no dynamic dispatch**. So even if a subclass had its own companion member of the same name, calling through the static entry point does not pick it up polymorphically. The companion's *instance* method can be `open`/overridable within the companion hierarchy, but the static shortcut is fixed to the class where it's declared. ## Combination rules - Cannot combine with `@JvmField` (methods vs field). - Redundant/disallowed on `const val`. - Compatible with `@JvmName` (you can rename the generated static method) and with `@Throws` (declare checked exceptions on it). ## Why it matters in design When authoring a Kotlin library consumed by Java, scattering `@JvmStatic` ad hoc creates an inconsistent surface. A deliberate policy — e.g., apply it to all public factory/entry-point functions in companions/objects — gives Java users an idiomatic, `Foo.create()`-style API.
- Why does an interface companion's @JvmStatic depend on the JVM target?Static methods in interfaces are only legal from bytecode 52 (Java 8); older targets like 1.6 couldn't host the generated static forwarder on the interface.
- If a subclass redeclares a same-named @JvmStatic member, does Java dispatch polymorphically through the static call?No. Static methods bind to the named class at compile time; there is no runtime dispatch, so the static entry point is not polymorphic.
saying these in an interview costs you the question
- Claiming @JvmStatic works on top-level functions
- Saying it can be applied to ordinary class methods
- Believing the static forwarder participates in virtual dispatch
- Not knowing the interface/JVM-target constraint
- Trying to combine it with @JvmField on the same member