How do annotations and use-site targets work in Kotlin, and why are targets like @get:, @field:, @param: necessary?
answer
- One property = field + getter + param
- Targets: field/get/set/param/property/receiver/setparam/file
- No target -> precedence param>property>field
- Only RUNTIME retention is reflectable
- @Target/@Retention/@Repeatable configure annotation classes
basics
~20 sA 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 sDeclaring `@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@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
Knows annotations attach metadata and Kotlin has @get:/@field: prefixes.
Explains why a property expands into multiple elements and what each target selects.
Recalls the default precedence and how retention controls reflective visibility, debugging framework integration issues.
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