skip to content

Use-Site Annotation Targets

Use-site targets like @get:, @field:, and @param: decide which emitted element an annotation lands on. This is the answer whenever a validation or persistence annotation on a Kotlin property mysteriously has no effect.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

In Kotlin, what is a use-site annotation target, and why might you write `@field:NotNull` instead of just `@NotNull` on a property?

level: juniorimportance: must knowfreq 55%

answer

  1. One property -> field + getter + setter + ctor param
  2. Default order: param > property > field (first match wins)
  3. @field: for JPA/Jackson reflection
  4. Bare annotation can silently miss the element a framework reads
  5. @property: is Kotlin-only metadata

basics

~10 s

A single Kotlin property turns into several Java elements (field, getter, setter, constructor parameter). A use-site target like @field: tells the compiler which of those elements the annotation should land on.

solid answer

~40 s

When you declare a Kotlin property, the compiler may emit a backing field, a getter, a setter, and (for primary-constructor properties) a constructor parameter. An annotation written with no prefix goes to a default element chosen by the compiler from the annotation's allowed @Target list. A use-site target — `@field:`, `@get:`, `@set:`, `@param:`, `@receiver:`, `@property:`, `@setparam:`, `@delegate:` — explicitly directs the annotation to one specific emitted element. `@field:NotNull` forces the annotation onto the backing field (what JPA/Jackson reflection often inspects), whereas `@get:NotNull` puts it on the getter. Choosing the wrong one means the framework looks at an element that has no annotation, so the constraint silently does nothing.

code

kotlin · 4 lines
kotlin
data class Account(
    @field:JsonProperty("acct_id") val id: Long,
    @get:JsonProperty("display") val name: String,
)

go deeper

for a junior

Can state that one property maps to several JVM elements and that the target prefix selects one.

for a middle

Knows the default priority order (param > property > field) and that the first match wins, so a bare annotation can miss the field.

for a senior

Connects target choice to concrete framework reflection (JPA/Jackson/Bean Validation) and predicts silent failures.

for a principal

Sets team conventions (e.g. always @field: for persistence DTOs) and reasons about cross-framework portability of annotation placement.

## Why targets exist A Kotlin *property* is a single source-level declaration, but on the JVM it expands into **multiple bytecode elements**: - a **backing field** (private, holds the value) - a **getter** (`getX()`) - a **setter** (`setX()`, only for `var`) - a **constructor parameter** (only for properties declared in the primary constructor) When you put an annotation on a property, the compiler has to decide *which* of those elements receives it. A **use-site target** is the `target:` prefix you write before the annotation name to make that choice explicit. ## The available targets - `@field:` — the backing field - `@get:` — the getter method - `@set:` — the setter method - `@param:` — the primary-constructor parameter - `@property:` — the Kotlin property itself (Kotlin-only metadata, invisible to Java) - `@setparam:` — the parameter of the setter - `@receiver:` — the receiver of an extension function/property - `@delegate:` — the field storing a delegate instance (for `by`) ## Default resolution when you omit the target If you write a bare `@Ann`, the compiler picks **one** target from this priority order, using the annotation's own `@Target` declaration to filter: 1. `param` (constructor parameter, if applicable) 2. `property` 3. `field` The **first** applicable one wins — not all of them. So a bare annotation that is valid on both `param` and `field` lands on `param` only. ```kotlin import jakarta.validation.constraints.NotBlank data class User( // bare: lands on the constructor PARAMETER (first applicable) @NotBlank val name: String, // explicit: lands on the backing FIELD, which reflection scans @field:NotBlank val email: String, ) ``` ## Why it matters for Java/frameworks Many Java frameworks read annotations via reflection on a **specific** element. JPA, Jackson, and Hibernate Validator often scan the **field**; Bean Validation may scan the getter. If your annotation lands on the constructor parameter but the framework reads the field, the annotation is effectively invisible and the rule never fires — a silent failure with no compile error.

  • If you write a bare `@NotBlank` on a primary-constructor `val`, which element gets it?
    The constructor parameter (`param`), because it is first in the default priority order and the annotation is applicable there.
  • Does `@property:` make the annotation visible to Java?
    No. `@property:` stores it as Kotlin metadata only; plain Java reflection on the field/getter won't see it.

A property is like an address with several mailboxes (field, getter, setter); the use-site target is the apartment number that tells the postman exactly which box to drop the letter in.

saying these in an interview costs you the question

  • Thinking a property is a single Java element
  • Assuming a bare annotation lands on all elements at once
  • Believing the default target is always the field
  • Not knowing why a validation/JSON annotation silently does nothing

context

open as a page

A teammate annotates a JPA/Jackson entity field with a bare constraint and it is silently ignored at runtime. Explain the cause and how `@field:` fixes it.

level: middleimportance: must knowfreq 50%

basics

~10 s

The bare annotation landed on the constructor parameter, but the framework reads the backing field by reflection. Since the field has no annotation, nothing happens. Prefixing with @field: puts it where the framework looks.

open as a page

How and why would you use `@get:JvmName` (and `@set:JvmName`) on a Kotlin property, and what problem does it solve at the Java boundary?

level: middleimportance: should knowfreq 35%

basics

~10 s

@get:JvmName("...") renames the getter method that Java sees, without changing the Kotlin property name. You use it to fix or customize the Java-facing accessor name, e.g. to avoid awkward or clashing generated names.

open as a page

Explain Kotlin's default use-site target resolution. Given an annotation valid on multiple targets, how does the compiler choose, and how do `@param:`, `@property:`, and `@field:` differ in visibility to Java reflection?

level: seniorimportance: should knowfreq 30%

basics

~20 s

If you omit the target, the compiler picks the first applicable one from param, then property, then field. @param: and @field: are real Java elements reflection can see; @property: is Kotlin-only metadata that plain Java can't read.

open as a page

What do the `@receiver:` and `@delegate:` use-site targets annotate, and when would you reach for each at the Kotlin/Java or framework boundary?

level: seniorimportance: nice to knowfreq 14%

basics

~10 s

@receiver: puts an annotation on the receiver parameter of an extension function or property (the hidden 'this' parameter). @delegate: puts it on the synthetic field that stores a delegated property's delegate instance.

open as a page