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?
answer
- Default order: param > property > field, first match only
- @field: + @param: visible to Java reflection
- @property: = Kotlin metadata, invisible to plain Java
- Single-@Target annotations skip the ambiguity
- Explicit target = safe at the boundary
basics
~20 sIf 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.
solid answer
~40 sWhen no use-site target is given, Kotlin resolves it against the annotation's `@Target` set using the fixed priority `param > property > field`, choosing the **first** applicable element only. `@param:` annotates the constructor parameter (visible via constructor reflection), `@field:` annotates the backing field (visible via field reflection — what JPA/Jackson use), and `@property:` is stored in the Kotlin `@Metadata` and is **invisible** to ordinary Java reflection (only Kotlin reflection / `kotlin-reflect` sees it). So an annotation that defaults to `property` (because it isn't applicable on `param`/`field`) will appear to vanish from Java's point of view. Annotations restricted to a single `@Target` (e.g. only fields) skip the ambiguity. This explains why bare annotations sometimes work, sometimes silently no-op, and why explicit targets are the safe choice at the Java boundary.
go deeper
Can name the targets but may not recall the exact default order or Java visibility differences.
States the param>property>field order and that only one element is chosen.
Explains how @Target intersects with emitted elements and the Java-reflection visibility of each target, predicting silent no-ops.
Reasons about library API stability and reflection contracts across frameworks, mandating explicit targets in public/persistence code.
## The resolution algorithm For a property declaration, the compiler determines the set of *applicable* use-site targets by intersecting: - the elements the property actually emits (param only if it's a primary-constructor property; setter only for `var`), with - the annotation's own `@Target(...)` meta-annotation (which Kotlin `AnnotationTarget`s it allows). If you wrote an explicit target, that target is used (and must be applicable, or you get an error). If you wrote **no** target, the compiler walks this fixed priority list and picks the **first** applicable entry: 1. `param` 2. `property` 3. `field` Only **one** element is annotated — not all applicable ones. ```kotlin class C(@Ann val x: Int) // If @Ann allows param + field: it lands on PARAM (param wins). // If @Ann allows only field: it lands on FIELD. // If @Ann allows only property: it lands on PROPERTY (Kotlin metadata only). ``` ## Java visibility of each target - **`@param:`** -> annotation on the **constructor parameter**. Visible via `Constructor.getParameterAnnotations()`. Most ORMs/serializers do *not* scan constructor params, so this is a common silent miss. - **`@field:`** -> annotation on the **backing field**. Visible via `Field.getAnnotations()`. This is what JPA field access, Jackson, and Hibernate Validator typically read. - **`@get:` / `@set:`** -> annotation on the **getter/setter method**. Visible via `Method.getAnnotations()`. Used by property/getter-access frameworks. - **`@property:`** -> stored in the class's **Kotlin `@Metadata`** blob, *not* on any Java element. **Invisible to plain Java reflection**; only readable via `kotlin-reflect`. - **`@receiver:`** -> annotates the receiver parameter of an extension; affects the extension's first (receiver) parameter. - **`@setparam:`** / **`@delegate:`** -> setter's value parameter / the delegate-storage field (`by`). ## Why this trips people up Because the default is the *first* applicable target, a bare annotation can: - land on `param` (invisible to a field-scanning ORM), or - land on `property` (invisible to all of Java), producing a runtime no-op with **no compile error**. Conversely, an annotation that is `@Target(FIELD)`-only has exactly one applicable target and "just works" without a prefix. ## Practical guidance - At any Java/framework boundary, write the target **explicitly** (`@field:`, `@get:`) rather than relying on defaults. - Reserve `@property:` for annotations you only consume via Kotlin reflection. - Remember setter-only and param-only targets don't exist for `val`/non-constructor properties; choosing them is a compile error.
- Why might an annotation 'disappear' entirely from Java even though it compiled fine?It defaulted to `property` (because it wasn't applicable on param/field), so it lives only in Kotlin @Metadata and plain Java reflection can't see it.
- When does the default resolution NOT include `param`?When the property isn't declared in the primary constructor (no parameter exists) or the annotation's @Target doesn't allow VALUE_PARAMETER.
saying these in an interview costs you the question
- Saying all applicable targets get the annotation
- Claiming the default is field-first
- Thinking @property: is visible to Java reflection
- Not knowing param-targeted annotations are missed by field-scanning frameworks