skip to content

How do annotations and use-site targets work in Kotlin, and why are targets like @get:, @field:, @param: necessary?

level: middleimportance: must knowfreq 50%

answer

  1. One property = field + getter + param
  2. Targets: field/get/set/param/property/receiver/setparam/file
  3. No target -> precedence param>property>field
  4. Only RUNTIME retention is reflectable
  5. @Target/@Retention/@Repeatable configure annotation classes

basics

~20 s

A single Kotlin property compiles into several JVM elements: a field, a getter, a constructor parameter. Use-site targets like @field: or @get: tell the compiler which one the annotation lands on, since each may need it.

solid answer

~40 s

Declaring `@Anno val x` is ambiguous because the property expands into multiple Java elements: backing field, getter (and setter), and — for a primary-constructor property — a constructor parameter. **Use-site targets** disambiguate: `@field:`, `@get:`, `@set:`, `@param:`, `@property:`, `@receiver:`, `@setparam:`, `@file:`. Without a target, Kotlin applies a precedence rule (param > property > field, picking the first applicable per the annotation's own `@Target`). This matters with libraries: e.g. Jackson/validation often need `@field:` or `@get:`, JPA `@Column` typically `@field:`. Annotations are defined with `annotation class`, configured by meta-annotations `@Target` (where it may appear: PROPERTY, FIELD, VALUE_PARAMETER...), `@Retention` (SOURCE/BINARY/RUNTIME — only RUNTIME is visible to reflection), and `@Repeatable`. You read them reflectively via `kCallable.annotations` or `findAnnotation<T>()`.

code

kotlin · 14 lines
kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Sensitive

class Account(@field:Sensitive val token: String)

import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.javaField

fun main() {
    val p = Account::class.declaredMemberProperties.first()
    // landed on the backing field, so check the Java field:
    println(p.javaField!!.isAnnotationPresent(Sensitive::class.java)) // true
}

go deeper

for a junior

Knows annotations attach metadata and Kotlin has @get:/@field: prefixes.

for a middle

Explains why a property expands into multiple elements and what each target selects.

for a senior

Recalls the default precedence and how retention controls reflective visibility, debugging framework integration issues.

for a principal

Reasons about API design of annotation contracts across Kotlin/Java interop and the retention/processing tradeoffs.

## Why use-site targets exist A Kotlin property is a high-level concept that the compiler lowers into **several distinct JVM elements**. For a primary-constructor property: ```kotlin class User(@Anno val name: String) ``` the `name` property can become: a **constructor parameter**, a **backing field**, and a **getter** method. An annotation written on the property is therefore ambiguous — which element should carry it? ## Use-site targets A *use-site target* is a prefix before the colon telling the compiler exactly where to put the annotation: - `@field:` — the backing field - `@get:` — the getter - `@set:` — the setter - `@param:` — the constructor parameter - `@property:` — the Kotlin property element (visible to Kotlin reflection) - `@receiver:` — an extension receiver - `@setparam:` — the setter's parameter - `@file:` — applies to the whole file (placed before the package declaration) ```kotlin class Dto( @get:JsonProperty("user_name") // on the getter @field:NotBlank // on the field val name: String ) ``` ## Default precedence when no target is given If you omit the target, the compiler uses the first applicable element in this order: **param → property → field** (filtered by what the annotation's own `@Target` permits). This is why a Java framework that reads fields can silently miss an untargeted annotation that landed on the parameter. ## Declaring annotations ```kotlin @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) annotation class Audited(val reason: String) ``` - `@Target` — the allowed code elements. - `@Retention` — `SOURCE` (discarded after compile), `BINARY` (in class file, not reflectable), `RUNTIME` (reflectable). **Only RUNTIME annotations are visible to reflection.** - `@Repeatable`, `@MustBeDocumented` — additional meta-annotations. ## Reading annotations reflectively ```kotlin import kotlin.reflect.full.findAnnotation val ann = User::class.declaredMemberProperties .first { it.name == "name" } .findAnnotation<Audited>() ``` Reflection over annotations works only for `RUNTIME`-retained ones.

  • Your validation annotation seems ignored by a Java framework that scans fields. What's a likely cause?
    Without a use-site target it may have landed on the constructor parameter (param has higher precedence), not the field; add @field:.
  • Why can't reflection see a BINARY-retained annotation?
    BINARY retention keeps the annotation in the class file but the JVM does not expose it at runtime; only RUNTIME retention is reflectable.

saying these in an interview costs you the question

  • Believing an untargeted annotation always lands on the field
  • Not knowing SOURCE/BINARY annotations are invisible to reflection
  • Confusing @param: with @property:
  • Thinking @file: goes on a class
  • Assuming one annotation covers field and getter automatically

context