What kinds of declarations can you apply a self-declared annotation like @Loggable to, and what does applying it actually do at runtime by default?
answer
- classes, functions, properties, params, type params, expressions
- application = attach metadata only
- no runtime effect by itself
- consumers: compiler, KSP/KAPT, frameworks, your reflection
- passive marker until something reads it
basics
~10 sYou can put @Loggable on classes, functions, properties, parameters, and more. By itself it does nothing at runtime — it just marks the code so something else can read and react to the mark.
solid answer
~40 sOnce declared with `annotation class Loggable`, you can place `@Loggable` before many declaration kinds: classes, functions, properties, constructors, value parameters, type parameters, expressions, and files (with a file-level target). Applying it simply **attaches metadata**; by default it has **no runtime effect on its own**. Annotations are passive markers — they only matter when something reads them: the Kotlin compiler, an annotation processor (KSP/KAPT), a framework like Spring or JUnit, or your own reflection code. So `@Loggable fun run()` does not log anything unless an aspect, processor, or reflective scanner finds the mark and acts. This separation — declaration vs. application vs. consumption — is central. (Which exact sites are *allowed*, and runtime visibility, are governed by @Target/@Retention, a sibling topic; the key idea here is that application is just marking.)
code
kotlin · 10 linesannotation class Loggable
@Loggable
class Service {
@Loggable
fun run(@Loggable input: String) {
// Nothing logs automatically.
// Some scanner/processor must read @Loggable to act.
}
}go deeper
Knows you can mark classes and functions and that it 'tags' code.
Lists multiple application sites and states that application is passive metadata with no automatic runtime effect.
Articulates the declaration/application/consumption split and names real consumers (KSP, Spring, JUnit, reflection).
Reasons about how this passive-marker model enables decoupled tooling and where target/retention boundaries fit.
## Application is just marking Declaring `annotation class Loggable` and writing `@Loggable` somewhere does **two** separate things conceptually: 1. **Declaration** — `annotation class Loggable` creates the marker type. 2. **Application** — `@Loggable` attaches that marker to a specific declaration. Neither step *runs* any code. An annotation is **passive**: by default it changes nothing about how your program executes. ## Where you can place it Kotlin lets annotations sit on a wide range of declaration kinds: ```kotlin @Loggable class Service // class @Loggable fun run() {} // function @Loggable val id: Int = 0 // property class C @Loggable constructor() // constructor fun f(@Loggable name: String) {} // value parameter fun <@Loggable T> g() {} // type parameter val x = (@Loggable 1 + 2) // expression ``` You can stack multiple annotations on one declaration too. ## What "does it do?" really means The crucial insight in interviews: **applying an annotation has no behavior by itself.** A mark is inert until a *consumer* reads it. Consumers include: - **The compiler** — for built-in annotations like `@Deprecated` or `@JvmStatic`. - **Annotation processors** — KSP or KAPT generate code at build time based on marks. - **Frameworks** — Spring scans for `@Component`; JUnit runs methods marked `@Test`. - **Your own reflection** — code that inspects marks at runtime and branches on them. So `@Loggable` that you invented does literally nothing until you (or a tool) write code that looks for it. ## Boundary with sibling topics - The exact **allowed** sites and whether a mark survives to runtime are controlled by `@Target` and `@Retention` (sibling topics). - **Use-site targets** like `@get:`/`@field:` (sibling topic) disambiguate where a mark lands on a property. Here the takeaway is: declare with `annotation class`, apply with `@Name` across many declaration kinds, and understand that application is pure marking with no automatic runtime effect.
- If @Loggable does nothing by itself, how do frameworks like JUnit make @Test methods run?The framework scans your classes (via reflection or processing), finds the marked declarations, and invokes them. The annotation is just the marker it searches for.
- Can you apply your annotation to a function parameter?Yes, you can write fun f(@Loggable x: Int), though whether it is allowed/retained depends on @Target/@Retention configured for the annotation.
Applying an annotation is like highlighting a sentence: the highlight alone changes nothing — it only helps a later reader decide what to do.
saying these in an interview costs you the question
- Believing applying an annotation runs logic by itself
- Saying an annotation can only go on classes
- Confusing declaration with consumption — thinking @Loggable inherently logs
- Not knowing any consumer exists (compiler, processor, framework, reflection)