skip to content

In Kotlin, how do you read an annotation off a class at runtime, and what must be true of that annotation for it to be visible?

level: juniorimportance: must knowfreq 55%

answer

  1. ::class -> KClass -> .annotations
  2. findAnnotation<T>() / hasAnnotation<T>()
  3. needs @Retention(RUNTIME)
  4. Kotlin default retention = RUNTIME
  5. kotlin-reflect on classpath

basics

~10 s

Use Kotlin reflection: get the class with ::class, look at its annotations list, or call findAnnotation. The annotation only shows up at runtime if it was declared with RUNTIME retention.

solid answer

~30 s

You access the class reference via MyClass::class (a KClass), then read its .annotations property (a List<Annotation>) or use the helper findAnnotation<MyAnno>() / hasAnnotation<MyAnno>() from kotlin.reflect.full. For the annotation to appear at runtime it must have @Retention(AnnotationRetention.RUNTIME). The default retention in Kotlin is RUNTIME, so unless someone set SOURCE or BINARY, it is readable. SOURCE annotations are discarded after compilation; BINARY ones are in the .class file but not exposed to reflection. You also need the kotlin-reflect library on the classpath, otherwise KClass reflective members throw KotlinReflectionNotSupportedError.

code

kotlin · 12 lines
kotlin
import kotlin.reflect.full.findAnnotation

@Retention(AnnotationRetention.RUNTIME)
annotation class Route(val path: String)

@Route("/users")
class UserController

fun main() {
    val r = UserController::class.findAnnotation<Route>()
    println(r?.path) // /users
}

go deeper

for a junior

Knows ::class.annotations / findAnnotation and that RUNTIME retention is required.

for a middle

Explains the three retention levels and that Kotlin defaults to RUNTIME, unlike Java.

for a senior

Mentions the kotlin-reflect classpath dependency and KotlinReflectionNotSupportedError, and the KAnnotatedElement abstraction.

for a principal

Frames retention choice as an API design / performance decision and discusses avoiding reflection in hot paths.

## What reflection means here Reflection is inspecting code structure (classes, functions, properties, their annotations) at runtime instead of compile time. Kotlin's reflection types live in `kotlin.reflect`: `KClass` (a class), `KFunction`/`KProperty` (callables), all sub-types of `KAnnotatedElement`, which is the interface that exposes annotations. ## Getting the element - `MyClass::class` gives a `KClass<MyClass>`. - `instance::class` gives the runtime class of an object. - A function/property reference like `::foo` or `MyClass::bar` gives a `KCallable`. ## Reading the annotations Every `KAnnotatedElement` has: ```kotlin public val annotations: List<Annotation> ``` This list contains only the annotations actually retained at runtime, already materialized as instances you can read parameters from. Convenience extensions in `kotlin.reflect.full`: ```kotlin import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.hasAnnotation @Retention(AnnotationRetention.RUNTIME) annotation class Route(val path: String) @Route("/users") class UserController val anno: Route? = UserController::class.findAnnotation<Route>() val present: Boolean = UserController::class.hasAnnotation<Route>() println(anno?.path) // /users ``` ## The retention rule An annotation is only visible to reflection when its `@Retention` is `AnnotationRetention.RUNTIME`. Kotlin's **default** retention is `RUNTIME` (unlike Java, where the default is CLASS/BINARY). The three levels: - `SOURCE` — kept only in source, erased by the compiler; never readable reflectively. - `BINARY` — written into the `.class` file but not surfaced to reflection. - `RUNTIME` — in the class file **and** visible to reflection. ## Classpath requirement Kotlin reflection needs the `org.jetbrains.kotlin:kotlin-reflect` artifact. Without it, touching reflective members (like `.annotations` on a `KClass`) throws `KotlinReflectionNotSupportedError`. Simple `::class` literal creation works without it, but reading members does not.

  • Why might findAnnotation return null even though the annotation is on the source code?
    Because the annotation's retention is SOURCE or BINARY, so it isn't surfaced to reflection; only RUNTIME-retained annotations appear.
  • What is the difference between .annotations and findAnnotation<T>()?
    .annotations is the full List<Annotation>; findAnnotation<T>() is a typed convenience returning the single instance of type T or null.

Annotations are sticky notes on code; RUNTIME retention is the difference between a note still on the file vs one thrown away before shipping.

saying these in an interview costs you the question

  • Saying Kotlin's default retention is SOURCE or BINARY (it is RUNTIME).
  • Claiming all annotations are always readable at runtime.
  • Confusing ::class (KClass) with .javaClass / Java Class as the same reflection entry point.
  • Forgetting kotlin-reflect must be on the classpath.
  • Thinking SOURCE annotations can be read with extra effort at runtime.

context