What do the `@receiver:` and `@delegate:` use-site targets annotate, and when would you reach for each at the Kotlin/Java or framework boundary?
answer
- Extension = static method; receiver is its first param
- @receiver: annotates that hidden receiver param
- by-delegate creates name$delegate field
- @delegate: annotates the delegate-storage field
- Delegated property has no normal backing field
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.
solid answer
~40 sExtensions compile to static methods whose first parameter is the **receiver**. `@receiver:` targets that hidden receiver parameter — useful when a framework or nullability/validation tool inspects parameter annotations and you need metadata on the receiver specifically (e.g. `fun @receiver:NotNull String.clean()`). `@delegate:` targets the **compiler-generated backing field** that holds a delegate created with `by` — e.g. `val log by lazy { ... }` produces a hidden `log$delegate` field, and `@delegate:Transient val cache by lazy { ... }` annotates that field so a serializer or persistence layer treats the stored delegate correctly. Both are niche but matter when reflection-driven frameworks read parameter/field annotations and the value you care about lives on the receiver parameter or the delegate field rather than the visible declaration.
code
kotlin · 9 linesclass Cache {
// annotate the hidden delegate field, e.g. so it isn't serialized
@delegate:Transient
val data by lazy { loadHeavyData() }
}
// receiver-parameter annotation on an extension
fun @receiver:NotEmpty String.slug(): String =
trim().lowercase().replace(' ', '-')go deeper
May not know these targets exist; can at most guess their names.
Knows @delegate: relates to by and @receiver: to extensions but may be fuzzy on the generated elements.
Explains that extensions compile to static methods (receiver = first param) and that by creates a name$delegate field, choosing the right target per scenario.
Anticipates serialization/AOP/persistence interactions with delegate fields and receiver params and codifies guidance for public APIs.
## `@receiver:` — the extension receiver parameter An **extension function/property** doesn't really belong to the receiver type; it compiles to a **static method** whose **first parameter** is the receiver (the thing you call `this` on inside the body). Sometimes you need an annotation on *that* parameter — for example to express nullability for a Java caller, or to feed a validation/AOP framework that reads parameter annotations. ```kotlin fun @receiver:NotEmpty String.normalized(): String = trim().lowercase() ``` Here `@receiver:NotEmpty` lands on the receiver (`this: String`) parameter of the generated static method, not on the return or any explicit argument. Without `@receiver:`, an annotation written before the receiver type would be ambiguous or land elsewhere. ## `@delegate:` — the delegate-storage field A **delegated property** (`val/var name by <delegate>`) is implemented by the compiler creating a hidden field, conventionally named `name$delegate`, that stores the **delegate instance** (e.g. the `Lazy` object from `lazy { }`, or an `Observable`). The property's getter/setter forward to that delegate via `getValue`/`setValue`. `@delegate:` directs an annotation onto **that hidden delegate field**: ```kotlin class Service { @delegate:Transient val client by lazy { buildClient() } } ``` Without `@delegate:`, you cannot annotate the synthetic delegate field. This matters when a serializer or persistence framework reflects over fields and would otherwise try to serialize/persist the delegate holder (e.g. marking it `transient` so Java serialization skips the `Lazy` instance). ## How they relate to the other targets - `@receiver:` is the only way to annotate the extension's receiver parameter. - `@delegate:` is the only way to annotate the `by`-generated field; `@field:` would refer to a normal backing field, which a delegated property does **not** have (its value lives in the delegate). - Both are **single-purpose**: there's no default resolution that lands on receiver or delegate, so you must write them explicitly. ## When to reach for each - **`@receiver:`** — extension functions exposed to Java/AOP/validation where receiver metadata (nullability, constraints) must be visible on the generated parameter. - **`@delegate:`** — delegated properties (`lazy`, `Delegates.observable`, custom delegates) where the **stored delegate** field needs framework metadata such as `@Transient`, ignore-for-serialization, etc.
- Why can't you use `@field:` on a `by lazy` property to make it transient?A delegated property has no ordinary backing field; the value lives in the synthetic delegate field, which only `@delegate:` can target.
- Where does `@receiver:` place the annotation in the compiled extension?On the first parameter (the receiver / `this`) of the static method the extension compiles to.
saying these in an interview costs you the question
- Confusing @receiver: with @param: for normal value parameters
- Thinking a delegated property has a normal backing field
- Trying to annotate the delegate field with @field:
- Believing default resolution can pick receiver or delegate