skip to content

When you annotate a Kotlin property without a use-site target, what is the default resolution order the compiler uses to choose the target?

level: middleimportance: must knowfreq 55%

answer

  1. Order: param → property → field
  2. First permitted target wins, only one chosen
  3. get/set/receiver/delegate never auto-selected
  4. PROPERTY target is Kotlin-only, invisible to Java
  5. Ctor property ⇒ param often wins

basics

~10 s

Kotlin tries targets in a fixed order and uses the first one the annotation is allowed on: the constructor parameter, then the property, then the field. Getters and setters are never chosen automatically.

solid answer

~40 s

If no use-site target is given, Kotlin walks a fixed priority list and applies the annotation to the **first** target that the annotation's own `@Target` permits. The order is: `param`, then `property`, then `field`. The compiler picks exactly one (not all that match). Crucially, `get`/`set`/`receiver`/`delegate` are **never** selected by default — you must request them explicitly. So if an annotation declares `@Target(VALUE_PARAMETER, FIELD)` and you place it on a primary-constructor property, it lands on the parameter, because `param` comes first. If it only allows `FIELD`, it lands on the field. The `property` target is Kotlin-only metadata (invisible to Java reflection), so an annotation that resolves to `property` won't be seen by Java libraries — a common interop surprise.

code

kotlin · 8 lines
kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
annotation class Marker

class User(@Marker val name: String) // -> VALUE_PARAMETER (param outranks field)

class Order {
    @Marker var total: Long = 0      // no ctor param here -> FIELD
}

go deeper

for a junior

Knows the default isn't random but may not recall the exact order.

for a middle

States param → property → field and that only one target is chosen.

for a senior

Explains the PROPERTY-is-Kotlin-only interop trap and why getters need explicit targeting.

for a principal

Sets conventions to always be explicit on interop boundaries to avoid metadata-vs-bytecode surprises.

## The default order When you write an annotation on a property **without** a `@get:`/`@field:`/etc. prefix, the Kotlin compiler does not guess. It evaluates a fixed list of candidate targets and applies the annotation to the **first** candidate that the annotation actually allows (per its `@Target` meta-annotation): 1. **`param`** — the constructor parameter (only exists for primary-constructor properties) 2. **`property`** — the Kotlin property (a Kotlin-only target stored in metadata) 3. **`field`** — the backing field The compiler chooses **exactly one** — the first match wins; it does not apply to all matching targets. ## What is never chosen automatically `get`, `set`, `receiver`, and `delegate` are **never** selected by the default rule. If you want the annotation on the getter or setter, you must write `@get:` / `@set:` explicitly. This is the root cause of "my getter annotation does nothing" bugs: the default never reaches the getter. ## Worked examples ```kotlin @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD) annotation class Tag class A(@Tag val x: Int) // resolves to PARAM (param beats field) class B { @Tag val y: Int = 0 // not a ctor property -> param skipped -> resolves to FIELD } ``` - In `A`, `x` is a constructor property, so `param` is a candidate and wins. - In `B`, `y` is declared in the body, so there is no parameter; `property` isn't in `@Target`, so it falls through to `field`. ## The interop trap with `property` `AnnotationTarget.PROPERTY` is a **Kotlin-only** concept. Annotations stored there live in Kotlin metadata and are **invisible to Java reflection**. So if an annotation only permits `PROPERTY` (and you don't override), Java libraries like Jackson or Hibernate won't see it. To make such an annotation reachable from Java, you typically force `@field:` or `@get:`. ## Practical guidance - For JVM-interop annotations, **be explicit** rather than relying on the default. - Remember the mnemonic order: **param → property → field**. - Getters/setters need an explicit prefix, always.

  • Why won't an annotation that resolves to the `property` target show up in Java reflection?
    AnnotationTarget.PROPERTY is a Kotlin-only target stored in the class metadata, not as a real JVM annotation on a field or method, so java.lang.reflect can't see it.
  • Does the default ever target the getter?
    No. get/set are never chosen by the default order; you must write @get: or @set: explicitly.

saying these in an interview costs you the question

  • Saying the default applies the annotation to all matching targets
  • Claiming the order is field → property → param (reversed)
  • Believing getters can be auto-targeted by default
  • Not knowing PROPERTY is invisible to Java reflection

context