skip to content

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?

level: middleimportance: should knowfreq 35%

answer

  1. classes, functions, properties, params, type params, expressions
  2. application = attach metadata only
  3. no runtime effect by itself
  4. consumers: compiler, KSP/KAPT, frameworks, your reflection
  5. passive marker until something reads it

basics

~10 s

You 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 s

Once 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 lines
kotlin
annotation class Loggable

@Loggable
class Service {
    @Loggable
    fun run(@Loggable input: String) {
        // Nothing logs automatically.
        // Some scanner/processor must read @Loggable to act.
    }
}

go deeper

for a junior

Knows you can mark classes and functions and that it 'tags' code.

for a middle

Lists multiple application sites and states that application is passive metadata with no automatic runtime effect.

for a senior

Articulates the declaration/application/consumption split and names real consumers (KSP, Spring, JUnit, reflection).

for a principal

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)

context