Which property declarations cannot be marked @JvmField, and why does each restriction exist?
answer
- Rule: must reduce to one plain public field
- No custom get/set, no computed, no `by` delegate
- Public only — not private/protected
- No open/override (fields aren't virtual)
- lateinit var is ALLOWED; const is not
basics
~20 s@JvmField needs a real backing field with default accessors. It's rejected on properties that are private, open/override, const, delegated, have custom get/set, are computed, or live in an interface — because there's no plain public field to expose.
solid answer
~40 s`@JvmField` requires the property to be expressible as a single public field, so the compiler rejects it when that's impossible or contradictory: (1) **custom `get()`/`set()`** — accessors do work a raw field can't; (2) **no backing field** (computed property) — nothing to expose; (3) **delegated property `by`** — storage lives in the delegate, not a field; (4) **`private`/`protected`** — a field exposed to Java must be public; (5) **`open`/`override`** — fields can't be virtually dispatched, so polymorphism would break; (6) **`const`** — already a static final field, redundant; (7) **inside an `interface`** — interfaces hold no instance state. `lateinit var` **is** allowed, and `@JvmField` may be combined with normal `var`/`val`. The unifying rule: the property must reduce to exactly one ordinary public Java field with default access semantics.
code
kotlin · 10 linesclass Demo {
@JvmField var ok = 0 // allowed
@JvmField lateinit var name: String // allowed
// each line below is a compile error:
// @JvmField val computed get() = ok // custom getter
// @JvmField val lazyVal by lazy { ok } // delegated
// @JvmField open val o = 1 // open
// @JvmField private val p = 1 // not public
// @JvmField const val K = 1 // const redundant
}go deeper
Recalls a couple of restrictions (e.g. no custom getter) even if not exhaustive.
Lists most disallowed cases and that lateinit is allowed.
Derives the restrictions from the 'must be one plain public field' principle and explains open/delegated/const reasoning.
Connects the constraints to Java ABI guarantees and API-evolution implications of exposing raw fields.
## The unifying principle `@JvmField` says: *expose this property as one plain public Java field, with no method involved.* Any feature that needs a **method** or that contradicts **plain public field semantics** is therefore forbidden. Walk the list with that lens. ### Disallowed cases - **Custom accessor** (`get()`/`set()` with a body): a field can't run code on read/write. A field can't do validation or lazy computation, so the annotation is rejected. - **No backing field / computed property** (`val area get() = w * h`): there is no stored slot to expose. - **Delegated property** (`val x by lazy { ... }` or any `by`): the value is stored inside the **delegate object**, accessed via `getValue`/`setValue` operator functions — not a field. (`lazy`, `Delegates.observable`, map delegation, etc.) - **`private` / `protected` visibility**: an exposed Java field must be **public**; a non-public @JvmField would contradict its purpose, so it's disallowed. - **`open` / `override`**: Java fields are **statically dispatched** (no virtual lookup). Allowing an overridable field would silently break polymorphism, so these are rejected. - **`const`**: a `const val` already compiles to a `public static final` field; `@JvmField` would be redundant — rejected together. - **Inside an `interface`**: interfaces have no instance fields in Kotlin's model; properties there are abstract/accessor-based. - **`@JvmStatic` is not a substitute**: that's for functions/accessors, a different mechanism. ### Allowed (commonly asked) - **`lateinit var`** — *allowed*; it has a non-null backing field initialized later, perfect to expose. - Instance `val`/`var`, top-level `val`/`var`, and companion/object properties (lifting to static) with default accessors. ```kotlin class Ok { @JvmField var a = 1 // ok @JvmField lateinit var b: String // ok } class Bad { @JvmField val c get() = 1 // ERROR: custom getter, no backing field @JvmField val d by lazy { 1 } // ERROR: delegated // @JvmField private val e = 1 // ERROR: not public // @JvmField open val f = 1 // ERROR: open } ``` ### Why these matter in practice The restrictions guarantee that what Java sees is a **stable, simple field** with predictable read/write semantics — no hidden method calls, no broken overriding, no surprising inlining. If you need any of those (encapsulation, laziness, polymorphism), `@JvmField` is the wrong tool and you keep the normal property.
- Why is @JvmField allowed on `lateinit var` but not on a `lazy` delegated property?`lateinit var` has a real backing field (just initialized later), which can be exposed. A `lazy` property stores its value in a delegate object accessed via getValue, so there's no plain field to expose.
- Why can't an `open` property be @JvmField?Java field access is statically bound — there's no virtual dispatch on fields — so exposing an overridable property as a field would break polymorphic behavior.
saying these in an interview costs you the question
- Saying @JvmField works on computed/delegated properties
- Claiming lateinit var is disallowed (it's allowed)
- Thinking a private property can be @JvmField
- Not knowing open/override is rejected and why
- Treating @JvmField and const as combinable