skip to content

Where do `const val` declarations end up in JVM bytecode for top-level, `object`, and `companion object` cases, and how does that affect Java interop and `@JvmStatic`?

level: seniorimportance: nice to knowfreq 22%

answer

  1. Always public static final, inlined
  2. Top-level -> FileKt class (or @JvmName)
  3. object -> Object.NAME
  4. companion const -> hoisted to OuterClass.NAME
  5. @JvmStatic/@JvmField not needed for const

basics

~20 s

A const val becomes a fixed static final field in the compiled class. Top-level ones live on the file's generated class; companion ones live on the outer class. Java can read them directly by name, and you don't need @JvmStatic.

solid answer

~40 s

Every `const val` compiles to a `public static final` field holding the literal. For a **top-level** `const val` in `Config.kt`, the field lives on the synthesized `ConfigKt` class, so Java reads `ConfigKt.FOO`. For a `const val` inside an **`object Singleton`**, the field is a `static final` on the object class, accessible from Java as `Singleton.FOO`. For a `const val` in a **`companion object`**, the constant is hoisted to a `static final` field on the **enclosing** class, so Java uses `Outer.FOO` (not `Outer.Companion.FOO`). Because the value is a compile-time constant, it is already `static final` and inlined — `@JvmStatic` is unnecessary and not needed (it's for promoting regular companion members/functions to static, not for `const`). And like Java's `static final` constants, the value is inlined into Java callers too.

code

kotlin · 10 lines
kotlin
// Config.kt
const val TIMEOUT = 30          // Java: ConfigKt.TIMEOUT

object Limits { const val MAX = 100 }   // Java: Limits.MAX

class Http {
    companion object { const val OK = 200 }  // Java: Http.OK (hoisted)
}

@file:JvmName("Config")  // would make Java use Config.TIMEOUT (must be at file top)

go deeper

for a junior

Knows const val becomes a static-like field Java can read by name.

for a middle

Knows top-level lands on the ...Kt class and that companion constants are reachable from Java simply by Outer.NAME.

for a senior

Articulates the hoisting of companion const to the enclosing class, why @JvmStatic/@JvmField are unnecessary, and the @JvmName override.

for a principal

Connects JVM placement to cross-language binary-compat (Java inlines too) and sets interop conventions for mixed Kotlin/Java modules.

## The general rule A `const val` always compiles to a `public static final` field whose value is the literal, and references are **inlined** at use sites (Kotlin and Java alike). ## Top-level `const val` Kotlin puts top-level declarations into a synthesized **file class** named `<FileName>Kt`. ```kotlin // Config.kt const val TIMEOUT = 30 ``` Java sees: ```java int t = ConfigKt.TIMEOUT; // public static final int TIMEOUT = 30 ``` You can rename the file class with `@file:JvmName("Config")` so Java uses `Config.TIMEOUT`. ## `const val` in an `object` ```kotlin object Limits { const val MAX = 100 } ``` The constant is a `static final` field on the `Limits` class: ```java int m = Limits.MAX; ``` ## `const val` in a `companion object` — the surprising case ```kotlin class Http { companion object { const val OK = 200 } } ``` The constant is **hoisted onto the enclosing class** `Http`, so Java accesses it as: ```java int code = Http.OK; // NOT Http.Companion.OK ``` This differs from a **non-const** companion `val`, which by default lives on the `Http.Companion` instance and from Java requires `Http.Companion.getX()` unless you add `@JvmStatic` (functions) or `@JvmField` (mutable/non-const fields) to promote/expose it. ## Why `@JvmStatic` is irrelevant for `const` `@JvmStatic` exists to make **regular** companion-object methods/properties callable as Java statics. A `const val` is **already** a `static final` field by virtue of being a compile-time constant, so adding `@JvmStatic` is unnecessary (and constants don't need `@JvmField` either). The compile-time-constant mechanism supersedes those interop annotations. ## Inlining parity with Java Because Kotlin `const` mirrors Java `public static final` String/primitive constants, Java callers also **inline** the value at compile time. This means the same binary-compatibility caveat applies across the language boundary: changing a Kotlin `const val` and recompiling only its module won't update already-compiled Java consumers until they recompile. ## Summary table - Top-level → `FileNameKt.NAME` (or `@JvmName`). - `object` → `ObjectName.NAME`. - `companion object` → `EnclosingClass.NAME` (hoisted). - All: `public static final`, inlined, no `@JvmStatic`/`@JvmField` needed.

  • How does Java access a non-const companion `val` without annotations?
    Through the companion instance: `Outer.Companion.getX()`. To call it as a Java static you add `@JvmStatic` (for the accessor) or expose a field with `@JvmField`. A `const val` skips all of this because it's already a static final constant.
  • Can you change the Java-visible class name of a top-level `const val`?
    Yes — add `@file:JvmName("Config")` at the top of the file so the generated class is `Config` instead of `ConfigKt`, letting Java write `Config.TIMEOUT`.

saying these in an interview costs you the question

  • Saying Java accesses companion `const` via `Outer.Companion.X`
  • Claiming `const` needs `@JvmStatic` or `@JvmField`
  • Not knowing top-level declarations land on a `<File>Kt` class
  • Thinking `const` values are not inlined for Java callers
  • Confusing the hoisting behavior of const with non-const companion members

context