skip to content

What expressions can supply an annotation argument or its default value? Explain the role of const val.

level: middleimportance: must knowfreq 50%

answer

  1. Compile-time constant expression only
  2. const val, literals, enum, Foo::class, nested annotation, array literal
  3. Plain val and function calls are rejected
  4. Constant arithmetic on constants allowed
  5. Defaults obey the same rule

basics

~10 s

Only compile-time constants: literals, enum entries, and values declared with const val. A normal val or a function result won't compile because the value must be known when the code is compiled.

solid answer

~40 s

Every annotation argument — and every parameter default declared with `= ...` — must be a **compile-time constant expression**. Valid sources are: literals (`42`, `"x"`, `true`), enum entries (`Level.HIGH`), class literals (`Foo::class`), nested annotation constructions (`Inner(1)`), array literals (`[1, 2]`/`arrayOf(...)`), and references to **`const val`** properties. A plain `val name = "x"` is rejected because it is a runtime-initialized property, even though its value looks constant; only `const val` (top-level or in an `object`/`companion object`, of a primitive or String type) is a true compile-time constant. Constant arithmetic on constants is allowed (`const val MAX = 10 * 60`). Defaults follow the exact same rule — you declare them with `val retries: Int = DEFAULT_RETRIES` where `DEFAULT_RETRIES` is a `const val`. There is no `lateinit`, no computed defaults, and no calls to ordinary functions.

code

kotlin · 9 lines
kotlin
object Defaults { const val TIMEOUT_MS = 30 * 1000 }

annotation class Timeout(val ms: Long = Defaults.TIMEOUT_MS.toLong())

@Timeout            // baked-in default
fun a() {}

@Timeout(ms = 5000) // literal override
fun b() {}

go deeper

for a junior

Knows literals and const val work and a plain val/function call does not.

for a middle

Articulates the compile-time-constant rule and the const val requirement (primitive/String, top-level or object).

for a senior

Explains inlining of const val into metadata and that defaults obey the same constraint, including constant arithmetic.

for a principal

Designs annotation defaults centralized in const objects for consistency, anticipating how processors read inlined constants.

## The single rule An annotation argument must be a **compile-time constant expression** — a value the compiler can fully evaluate without running any code. The same rule applies to a parameter's **default value** declared with `= ...`. ## What qualifies as a compile-time constant - **Literals**: `1`, `1L`, `3.14`, `'c'`, `"text"`, `true`. - **`const val` references** — properties declared `const val`, which must be top-level or inside an `object`/`companion object` and of a primitive type or `String`. - **Enum entries**: `DeprecationLevel.ERROR`. - **Class literals**: `Foo::class`. - **Nested annotation calls**: `Inner(min = 1)`. - **Array literals**: `[1, 2, 3]` (allowed inside annotations) or `arrayOf(1, 2, 3)`. - **Constant expressions over the above**: `const val MINUTE = 60`; `@Timeout(MINUTE * 5)` is fine because both operands are constants. ## What does NOT qualify - A plain `val x = "y"` — a normal property is initialized at runtime, so it is **not** a compile-time constant, even if its value is literally fixed. - A function call: `@Name(buildName())` — fails, runtime evaluation. - `System.getenv(...)`, `Math.PI` used where a strict constant is required (it's a `val`, not `const`), etc. ```kotlin const val DEFAULT_RETRIES = 3 // compile-time constant val runtimeRetries = compute() // NOT a constant annotation class Retry(val times: Int = DEFAULT_RETRIES) // default OK @Retry // uses default 3 class A @Retry(times = 5) // literal OK class B // @Retry(times = runtimeRetries) // COMPILE ERROR: not a const ``` ## Why `const val` exists for this `const val` tells the compiler the value is known at compile time and can be **inlined** into call sites and into class-file metadata. That is exactly what annotations need. A normal `val` only guarantees the reference is assigned once at runtime — too late for annotation metadata. ## Defaults are not special A default such as `val level: Level = Level.LOW` is just another annotation-constant slot; it obeys the identical constraint. Omitting the argument at a use site makes the compiler bake the default constant into the metadata.

  • Why can't a regular val be used as an annotation argument?
    A regular val is initialized at runtime, so its value is not known at compile time; only const val (and literals/enums) qualify.
  • Is const val MAX = 10 * 60 a valid constant for an annotation?
    Yes; arithmetic over compile-time constants is itself a compile-time constant.

Like a form that only accepts pre-printed checkbox options — you can't write a formula and have the clerk compute it later.

saying these in an interview costs you the question

  • Saying any val works as an annotation argument
  • Confusing const val with normal val
  • Thinking defaults can call functions or read runtime state
  • Believing System.getenv or similar can supply a default
  • Not knowing enum entries and class literals are valid constants

context