Explain the difference between SOURCE, BINARY, and RUNTIME retention, and which one you must use to read an annotation via reflection.
answer
- SOURCE → compiler only, then gone
- BINARY → in .class, invisible to reflection
- RUNTIME → in .class, visible to reflection
- Reflection needs RUNTIME
- Kotlin default = RUNTIME
basics
~10 sSOURCE is dropped after compiling, BINARY stays in the class file but reflection can't see it, RUNTIME stays and reflection can read it. You need RUNTIME for reflection.
solid answer
~40 sThe three AnnotationRetention values form a visibility ladder. SOURCE means the compiler uses the annotation (lint, processors, suppression) then discards it — nothing is written to bytecode. BINARY means the annotation is emitted into the `.class` file, so other compilers/tools reading the bytecode metadata see it, but the JVM does not retain it for reflection, so `getAnnotations()` / `kClass.annotations` won't find it. RUNTIME means it is both written to bytecode and retained by the JVM so reflection (`kotlin.reflect.full.findAnnotation`, `KClass.annotations`, or Java's `AnnotatedElement.getAnnotation`) can read it. To inspect annotations at runtime — DI frameworks, serializers, validators — you must declare RUNTIME (which is also Kotlin's default).
code
kotlin · 12 lines@Retention(AnnotationRetention.SOURCE)
annotation class CompileOnly
@Retention(AnnotationRetention.RUNTIME)
annotation class ReadableAtRuntime
@CompileOnly @ReadableAtRuntime class Demo
fun main() {
// Only ReadableAtRuntime shows up
println(Demo::class.annotations.map { it.annotationClass.simpleName })
}go deeper
Can state the three levels survive progressively longer and reflection needs RUNTIME.
Distinguishes BINARY (in bytecode, not reflective) from RUNTIME (in bytecode and reflective) and picks correctly per use case.
Explains tooling/ABI implications of BINARY and runtime cost tradeoffs, names concrete reflection APIs.
Reasons about retention as part of public-API contracts and tooling pipelines (KSP vs runtime frameworks).
## The retention ladder `@Retention(AnnotationRetention.X)` where X is one of three values, ordered by how far the annotation survives: ### SOURCE - Lives only in the source/compilation phase. - The compiler can act on it (suppress warnings, drive an annotation processor / KSP, emit lint) and then **discards** it. - **Not** present in the `.class` file, so neither bytecode tools nor reflection can see it. - Examples in the stdlib: `@Suppress`, `@DslMarker`-style source markers. ### BINARY - Written into the compiled `.class` file metadata. - Visible to tools that parse bytecode (other compilers, static analyzers, ABI checkers). - **Not** retained by the JVM at runtime, so reflection (`Class.getAnnotations()`, `KClass.annotations`) returns nothing for it. - Useful for cross-module compiler contracts that shouldn't pay the runtime cost. ### RUNTIME (default) - Written into the `.class` file **and** kept by the JVM so it can be read reflectively. - This is the only level readable with reflection. ## Reading reflectively ```kotlin import kotlin.reflect.full.findAnnotation @Retention(AnnotationRetention.RUNTIME) annotation class Marker @Marker class A @Retention(AnnotationRetention.SOURCE) annotation class GoneAtRuntime @GoneAtRuntime class B fun main() { println(A::class.findAnnotation<Marker>()) // Marker@... println(B::class.annotations) // [] — SOURCE is gone } ``` `findAnnotation<T>()`, `KClass.annotations`, `KFunction.annotations`, and Java's `AnnotatedElement.getAnnotation` all require **RUNTIME** retention to return anything. ## How to choose - Need a DI/serialization/validation framework or your own reflective code to see it at runtime? **RUNTIME**. - A compile-time-only marker (KSP/processor, lint, suppression)? **SOURCE** — avoids bloating bytecode. - A bytecode-level contract for other compilers but not runtime? **BINARY** (rare in app code).
- Why might you choose BINARY over RUNTIME for a compiler-level annotation?BINARY keeps it in bytecode for tools/other compilers without paying the JVM's runtime-retention cost or exposing it to reflection.
- Will a SOURCE-retained annotation ever appear in KClass.annotations?No — it is discarded after compilation, so reflection returns an empty list for it.
SOURCE is a sticky note you toss after reading; BINARY is ink that dries inside a sealed box; RUNTIME is ink on the outside you can read anytime.
saying these in an interview costs you the question
- Saying BINARY annotations are readable via reflection
- Believing reflection can recover SOURCE annotations
- Not knowing RUNTIME is required (and default) for reflective reads
- Confusing retention with use-site targets