skip to content

What are the practical differences and pitfalls when reading annotations via Kotlin reflection (KClass) versus Java reflection (Class), and when would you choose each?

level: seniorimportance: should knowfreq 30%

answer

  1. property = field + getter (+ setter); Java sees JVM elements
  2. use-site target decides which JVM element holds the annotation
  3. kotlin-reflect = extra jar + init cost
  4. bridges: .java, .javaField, .javaMethod, .kotlin
  5. both only see RUNTIME annotations

basics

~20 s

Kotlin reflection (KClass) understands Kotlin concepts like properties and use-site targets; Java reflection (Class) sees the compiled view of fields, getters, and methods. Kotlin reflection needs kotlin-reflect; Java reflection is built in and faster to start.

solid answer

~40 s

Java reflection (java.lang.Class.getAnnotation / isAnnotationPresent / getDeclaredAnnotations) only sees RUNTIME-retained annotations on the compiled JVM elements: fields, getter/setter methods, constructor parameters. It has no concept of a Kotlin 'property', so a property annotation may surface on the field OR the getter depending on use-site target. Kotlin reflection (KClass.annotations, findAnnotation, KProperty/KParameter) models Kotlin declarations directly. Kotlin reflection requires the kotlin-reflect jar and has higher initialization cost; Java reflection is built into the JDK and cheaper to start but ignorant of Kotlin-only constructs. Choose Kotlin reflection when working with Kotlin properties/parameters/types; drop to Java reflection (via .java / .javaField / .javaMethod) for performance, missing-kotlin-reflect environments, or to reach the exact JVM element a use-site target produced.

go deeper

for a junior

Knows both Java and Kotlin reflection exist but blurs property vs field.

for a middle

Explains property-to-JVM mapping and that both only see RUNTIME annotations.

for a senior

Uses bridge functions, reasons about use-site targets landing on field/getter/param, and weighs kotlin-reflect cost.

for a principal

Decides reflection strategy for a library/framework (kotlin-reflect vs Java vs KSP) balancing startup, footprint, and Kotlin fidelity.

## Two reflection models over one bytecode A Kotlin property compiles to up to three JVM elements: a private backing field, a getter method, and (for `var`) a setter method. Constructor `val` params also produce a constructor parameter. **Java reflection** sees these JVM elements directly; **Kotlin reflection** presents the original Kotlin declaration (`KProperty`, `KParameter`, `KFunction`). ## Where an annotation lands (the core pitfall) With no use-site target on a property like `@Json val id` placed on a constructor `val`, Kotlin applies a default target order (param > property > field), often the constructor parameter — so `Class.getDeclaredField("id").getAnnotation(Json)` may be null while the constructor parameter has it. Explicit targets: - `@get:Json` -> getter method (`Method.getAnnotation`). - `@field:Json` -> backing field (`Field.getAnnotation`). - `@param:Json` -> constructor parameter (`Parameter.getAnnotation`). Kotlin reflection lets you query the matching element: `prop.getter.findAnnotation`, `prop.javaField`, `ctorParam.findAnnotation`. ## Crossing between the two Bridges: `kClass.java`, `kProperty.javaField`, `kProperty.getter.javaMethod`, `kFunction.javaMethod`, `Class.kotlin`. Useful when you have one and need the other. ```kotlin import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.jvm.javaField val field = Dto::class.declaredMemberProperties .first { it.name == "id" }.javaField // java.lang.reflect.Field? val jAnno = field?.getAnnotation(Json::class.java) ``` ## Performance & deployment - `kotlin-reflect` adds a jar (~a couple MB) and pays a one-time init cost building the Kotlin metadata model; it is heavier than plain Java reflection. - In tight, hot, or startup-sensitive paths (or libraries that don't want a kotlin-reflect dependency), Java reflection or annotation processing/`kotlin-metadata-jvm` is often preferred. - Both only see `RUNTIME` annotations; retention rules are identical at the bytecode level. ## Choosing - Need Kotlin properties, parameters, nullability, types, use-site semantics -> Kotlin reflection. - Need minimal dependencies, fastest startup, or the exact JVM element -> Java reflection (possibly via the bridge functions). - Need zero runtime cost -> avoid reflection entirely (KSP/annotation processing / compile-time codegen).

  • You have a KProperty and need the java.lang.reflect.Field to read an annotation; how?
    Use kotlin.reflect.jvm.javaField: property.javaField?.getAnnotation(MyAnno::class.java).
  • Why might a team avoid kotlin-reflect in a library?
    It adds a dependency and runtime/startup cost; using Java reflection or KSP keeps the artifact lean and faster to initialize.

saying these in an interview costs you the question

  • Claiming Java and Kotlin reflection see different retention levels (both see only RUNTIME).
  • Not knowing a Kotlin property maps to field + getter (+ setter) on the JVM.
  • Being unaware of bridge functions like javaField/javaMethod/.kotlin.
  • Assuming kotlin-reflect is always present and free.
  • Reading getDeclaredField for a constructor-param-targeted annotation and concluding it's absent.

context