skip to content

Explain property initialization order and how Kotlin properties surface to Java callers (including the `@JvmField` escape hatch).

level: seniorimportance: should knowfreq 45%

answer

  1. init blocks + initializers run top-to-bottom, after ctor params
  2. reading a later property gives its default value
  3. Java sees getX()/setX(); isFoo keeps is-name
  4. @JvmField = public field, no accessors, no custom getter/open/const/lateinit
  5. const val = static final inlined; @JvmStatic for companion accessors

basics

~20 s

Properties and init blocks run top to bottom in the order they appear, after the primary constructor's parameters are set. From Java, Kotlin properties look like getX()/setX() methods unless you annotate the field with @JvmField to expose it directly.

solid answer

~40 s

Initializers and `init` blocks execute **in source order**, after the primary-constructor parameter properties are assigned. So a property initializer can use earlier properties but referencing a later one yields its default/`null` (a real footgun with custom accessors or open classes). On the JVM, each Kotlin property becomes a private backing field plus `getX()`/`setX()` accessors; `is`-prefixed `Boolean` properties keep the `is` name (`isReady()`/`setReady(...)`). `@JvmField` removes the accessors and exposes the field directly as a public Java field — valid only on properties with a backing field, no custom accessors, non-`open`, and not `lateinit`/`const`/`override`. For `companion object` constants use `const val` (compile-time constant inlined as a static final) or `@JvmStatic`/`@JvmField` to flatten access from Java.

code

kotlin · 8 lines
kotlin
class Order(val qty: Int) {
    val subtotal = qty * price // BUG: price read before it's set -> 0
    val price = 10
}
// Fix: declare price before subtotal, so init order assigns it first.

class Vec(@JvmField var x: Double, @JvmField var y: Double)
// Java: vec.x = 1.0;  (direct field, no getX())

go deeper

for a junior

Knows initializers run top to bottom and that Java sees getX()/setX().

for a middle

Can explain the forward-reference default-value pitfall and the is-prefixed boolean naming.

for a senior

Applies @JvmField, const val, @JvmStatic correctly and reasons about init order with open classes.

for a principal

Designs cross-language API surfaces (serialization, framework reflection, constants) and codifies interop conventions for the team.

## Initialization order For a class with a primary constructor, initialization proceeds in this order: 1. Primary-constructor **parameter properties** (`class C(val a: Int)`) are assigned from the arguments. 2. **Property initializers** and **`init { }` blocks** run **in the order they are written**, interleaved, top to bottom. ```kotlin class C(val a: Int) { val b = a + 1 // (1) runs: a is set init { println(b) } // (2) runs: prints b val c = b + 1 // (3) runs after the init above } ``` Because it's strictly top-down, an earlier initializer that reads a **later** property sees that property's default value (`0`/`false`/`null`) — a classic bug, especially when an `open` property is read from a superclass constructor before the subclass initializer runs. ## How properties appear to Java A Kotlin property with a backing field compiles to: - a **private field**, plus - public **`getX()`** and (for `var`) **`setX(...)`** methods. `Boolean` properties named `isFoo` keep that form: getter `isFoo()`, setter `setFoo(...)`. ```kotlin class User(var name: String, val isAdmin: Boolean) // Java: user.getName(); user.setName("x"); user.isAdmin(); ``` Computed properties (no field) expose only the getter. ## `@JvmField` — expose the field directly `@JvmField` tells the compiler to emit a **public field** and **no accessors**, so Java reads `obj.x` instead of `obj.getX()`: ```kotlin class Point(@JvmField var x: Int, @JvmField var y: Int) // Java: point.x = 5; point.y; ``` Constraints: the property must have a **backing field**, **no custom getter/setter**, must **not** be `open`, `override`, `const`, or `lateinit`, and must not be in an interface. ## Companion constants and Java - `const val MAX = 10` (only for primitives/`String`, top-level or in `object`/`companion object`) becomes a **`public static final`** inlined at the call site — the most Java-friendly constant. - A plain `val` in a `companion object` is accessed from Java as `Outer.Companion.getX()` unless you add `@JvmStatic` (static accessor) or `@JvmField` (static field). ## Why this is on the senior bar Getting init order wrong causes subtle null/zero bugs; getting interop wrong leaks awkward `Companion.getX()` calls or breaks serializers/frameworks that expect plain fields. Knowing `@JvmField`, `const`, and `@JvmStatic` lets you design clean cross-language APIs.

  • What value does `subtotal` get in the buggy example above?
    0 — `price` hasn't been initialized when `subtotal`'s initializer runs, so it reads price's default Int value 0.
  • When can't you use `@JvmField`?
    On properties with custom accessors, `open`/`override`/`const`/`lateinit` properties, or members of an interface — they have no plain public-field representation.
  • How do you expose a companion `val` as a simple Java static field?
    Annotate it `@JvmField` (static field) or, for compile-time constants, use `const val`; `@JvmStatic` instead gives a static accessor method.

saying these in an interview costs you the question

  • Claiming init blocks run before constructor parameters are set
  • Saying source order doesn't matter for initializers
  • Thinking `@JvmField` works with custom getters or on `open` properties
  • Confusing `const val` with `@JvmStatic`
  • Assuming Java always calls `getX()` even with `@JvmField`

context