Contrast how a companion `const val MAX_RETRIES = 3`, a plain companion `val name = "x"`, and a companion `@JvmField val cache = ...` each surface to Java.
answer
- const -> static final, inlined, no getter
- plain val -> Companion.getX() getter
- @JvmField -> raw static field, no accessor
- const limited to primitives/String
- @JvmStatic on val adds static getter
basics
~10 sconst val becomes a public static final you read directly (Foo.MAX_RETRIES). A plain val needs Foo.Companion.getName(). @JvmField exposes the field directly as Foo.cache, skipping the getter.
solid answer
~40 sThree different Java surfaces. (1) `const val` is a compile-time constant: it becomes a `public static final` field on the OUTER class, read as `Foo.MAX_RETRIES` — no getter, no Companion hop, and the value is inlined at call sites. Only allowed for primitives/String. (2) A plain companion `val` becomes a private backing field plus a getter on the Companion, so Java must call `Foo.Companion.getName()`. (3) `@JvmField val` exposes the backing field directly as a `public static` field on the outer class: `Foo.cache` — no getter generated, and it can't be const because it's not a compile-time constant. Choosing among them shapes the Java API: const for true constants, @JvmField for mutable/object statics, plain val when you want encapsulation.
code
kotlin · 11 linesclass Config {
companion object {
const val MAX_RETRIES = 3
val name = "service"
@JvmField val cache = HashMap<String, String>()
}
}
// Java:
// Config.MAX_RETRIES; // const -> static final
// Config.Companion.getName(); // plain val -> getter
// Config.cache.put("k","v"); // @JvmField -> raw fieldgo deeper
Knows const is read directly while a plain val needs Companion.getName().
Correctly distinguishes all three surfaces including @JvmField raising the field to the outer class.
Reasons about const inlining and binary compatibility when choosing the form for a published API.
Defines library-wide rules: const only for stable constants, @JvmField for object statics, to protect downstream Java ABI.
## The three forms All three live inside a `companion object`, but Java sees them very differently. ```kotlin class Config { companion object { const val MAX_RETRIES = 3 // compile-time constant val name = "service" // plain property @JvmField val cache = HashMap<String, String>() } } ``` ### 1. `const val` -> static final on the outer class - **`const`** means a compile-time constant; the value is known at compilation and **inlined** into callers. - Restricted to `String` and primitive types; must be top-level or in a companion/object; the initializer must be a constant expression. - Java sees `public static final int MAX_RETRIES = 3` on `Config` itself. ```java int r = Config.MAX_RETRIES; // direct, no getter, no Companion ``` ### 2. Plain `val` -> getter on the Companion - Generates a private static backing field in the companion plus a getter method `getName()` on the `Companion` instance. - Java must hop through Companion: ```java String n = Config.Companion.getName(); ``` - Add `@JvmStatic` to the val to also get a static `getName()` on `Config`: `Config.getName()`. ### 3. `@JvmField val` -> direct static field, no accessor - **`@JvmField`** suppresses getter/setter generation and exposes the backing field directly. - On a companion property, the field is hoisted to the OUTER class as a `public static` field. ```java Config.cache.put("k", "v"); ``` - Cannot combine with `const` (const already produces a static final; @JvmField is for non-constant values), and cannot be `private` or `open`. ## Why it matters - **`const`**: best for genuine constants — direct, inlined, fastest, but beware: inlining means changing the value requires recompiling Java consumers. - **`@JvmField`**: for object/mutable statics where you don't want accessors. - **plain `val`**: keeps encapsulation (a getter) when you may want logic later. ## Summary table | Declaration | Java access | Accessor? | |---|---|---| | `const val` | `Config.MAX_RETRIES` | none (static final, inlined) | | `val` | `Config.Companion.getName()` | getter | | `val` + `@JvmStatic` | `Config.getName()` | static getter | | `@JvmField val` | `Config.cache` | none (raw field) |
- Why can't you write `const val NOW = System.currentTimeMillis()`?const requires a compile-time constant expression; a runtime call isn't constant. Use @JvmField or a plain val instead.
- What's a hidden risk of changing a const value in a library?const values are inlined into compiled consumers, so they keep the old value until recompiled — a binary-compatibility trap.
saying these in an interview costs you the question
- Claiming a plain companion val is directly readable as Foo.name
- Saying const generates a getter
- Using @JvmField with const
- Thinking @JvmField keeps a getter
- Forgetting const inlining causes stale values in Java callers