skip to content

Why must annotation arguments (and certain `when`/`switch`-like contexts) use `const val` rather than a plain `val`?

level: middleimportance: should knowfreq 40%

answer

  1. Annotation args are compile-time metadata
  2. Plain val = runtime, rejected in annotations
  3. const val = inlined literal, accepted
  4. @RequestMapping(PATH) needs const
  5. Single source of truth for magic strings

basics

~20 s

Annotations are baked into the compiled code, so their values must be known at compile time. A plain val is only set when the program runs, so it can't be used there. const val is known at compile time, so it works.

solid answer

~40 s

Annotation arguments are written into the class file's metadata at **compile time**, so they must be **compile-time constants**. A plain `val` is initialized at runtime and read via a getter, so its value isn't available when the annotation is encoded — the compiler rejects it. `const val` is a compile-time constant whose literal value is known and inlined, so it satisfies the requirement. The same principle applies to other constant-expression contexts: for example, branches that need constant labels or any position the language specifies must be a constant expression. Using `const val` (often in a `companion object`) lets you name these values once and reuse them in annotations like `@RequestMapping(PATH)` or `@Deprecated(MESSAGE)` while keeping a single source of truth.

code

kotlin · 7 lines
kotlin
object Routes { const val USERS = "/users" }

@RequestMapping(Routes.USERS)   // OK
class UserController

val dynamicPath = loadPath()
// @RequestMapping(dynamicPath) // ERROR: must be a compile-time constant

go deeper

for a junior

Knows annotations need values fixed at compile time and that const val provides them.

for a middle

Explains that annotation arguments are encoded into class metadata at compile time, so runtime vals are rejected while const val is inlined.

for a senior

Generalizes to all constant-expression contexts and notes the additional allowed annotation arg kinds (enum, KClass, arrays).

for a principal

Drives convention: centralize route/header/message constants as const val for a single source of truth, balancing reuse against inlining/binary-compat trade-offs.

## Annotations are compile-time metadata An annotation and its arguments are **encoded into the class file** by the compiler. They are not evaluated at runtime when the annotated element loads — the values are already frozen in the bytecode metadata. Therefore every annotation argument must be a **compile-time constant** (or an array/other annotation/enum/`KClass` thereof). ```kotlin const val PATH = "/users" @RequestMapping(PATH) // OK: PATH is a compile-time constant class UserController ``` A plain `val` cannot be used: ```kotlin val path = "/users" // runtime value, getter-backed @RequestMapping(path) // ERROR: an annotation argument must be a compile-time constant class Broken ``` The compiler reports that the argument must be a constant — because at the moment the annotation is written into the class file, a runtime-initialized `val` simply has no value yet. ## Why `const val` qualifies `const val` is, by definition, a value the compiler resolves and **inlines** as a literal. So when the annotation is encoded, the compiler substitutes the literal directly. This makes `const val` the canonical way to share annotation argument values: ```kotlin object Routes { const val USERS = "/users" const val ORDERS = "/orders" } @RequestMapping(Routes.USERS) class UserController ``` ## Other constant-expression contexts Beyond annotations, the language requires constant expressions in several places. A `const val` works wherever a compile-time constant is needed, while a plain `val` does not. The unifying rule is: **if the construct is resolved at compile time, it needs a compile-time constant, i.e. `const val` (String/primitive) — not a runtime `val`.** ## Practical benefit Defining route paths, header names, or deprecation messages as `const val` gives a **single source of truth** that is simultaneously usable in normal code and in annotations, avoiding magic-string duplication while satisfying the compile-time-constant requirement.

  • Can you pass an enum entry or a `KClass` as an annotation argument even though it's not `const`?
    Yes. The constant-expression rule for `const val` covers String/primitives, but annotation arguments may also be enum constants, `KClass` references (`SomeClass::class`), other annotations, and arrays of these — they are all resolvable at compile time.
  • Why doesn't a plain `val` initialized with a literal work in an annotation?
    Even if its initializer is a literal, a plain `val` is still a runtime property read through a getter, so the compiler treats it as a runtime value, not a compile-time constant. You must mark it `const`.

saying these in an interview costs you the question

  • Saying a plain `val` works in annotations if it has a literal initializer
  • Not knowing annotation arguments are encoded at compile time
  • Confusing const with runtime field reads
  • Claiming you cannot share constants in annotations at all
  • Thinking enum/KClass args require const (they're allowed as constants too)

context