When would you deliberately use java.lang.reflect instead of kotlin-reflect in a Kotlin codebase, and how do you bridge between the two?
answer
- kotlin-reflect ~3MB + first-use metadata parse
- Java reflect: zero-dep, JVM model
- Bridges: .java/.kotlin, javaMethod/kotlinFunction
- Native image / Android favor lean Java reflect
- Introspect with Kotlin, cache Java handles for hot paths
basics
~10 sUse Java reflection when you don't need Kotlin-specific info, want to avoid the extra kotlin-reflect dependency and startup cost, or interop with Java frameworks. Bridge with .java and .kotlin and the jvm extension functions.
solid answer
~40 skotlin-reflect is a separate ~3MB artifact that loads and decodes @Metadata, which adds binary size and first-use startup cost. So you drop to java.lang.reflect when: you only need class/method/field/annotation data the JVM already exposes; you're on a startup- or size-sensitive target (Android, serverless, GraalVM native image where reflection must be registered); or you're feeding a Java framework that speaks Class/Method. Bridges: `KClass.java` and `Class.kotlin`; from kotlin.reflect.jvm: `KFunction.javaMethod`, `KFunction.javaConstructor`, `KProperty.javaField`/`javaGetter`, and reverse `Method.kotlinFunction`, `Field.kotlinProperty`. Conversely use kotlin-reflect when you need nullability, default args (callBy), properties, suspend detection, KType/typeOf, or variance. A common pattern: introspect with kotlin-reflect once, cache resolved java.lang.reflect handles for hot-path invocation.
code
kotlin · 9 linesimport kotlin.reflect.jvm.javaMethod
import kotlin.reflect.full.functions
class Svc { fun ping() = "pong" }
// Introspect with kotlin-reflect, then cache the java.lang.reflect Method
val kFn = Svc::class.functions.first { it.name == "ping" }
val method = kFn.javaMethod!! // bridge KFunction -> Method
method.invoke(Svc()) // fast invoke on hot pathgo deeper
Knows the two reflection APIs coexist and you can convert with .java and .kotlin.
Lists cases (no Kotlin info needed, Java framework interop) and names the main bridge functions.
Weighs dependency size and first-use cost and uses the introspect-then-cache hybrid pattern.
Sets codebase policy on reflection use across Android/native/serverless targets, balancing fidelity, startup, and binary size.
## The trade-off in one line kotlin-reflect = richer, Kotlin-faithful model **at a cost**; java.lang.reflect = leaner, JVM-only model that's always present. ## Why java.lang.reflect is sometimes the right call - **Dependency & size**: `kotlin-reflect` is a separate artifact (~3MB) you must add explicitly. `java.lang.reflect` is in the JDK — zero dependency. - **Startup / first-use cost**: kotlin-reflect lazily parses the `@Metadata` blob the first time you touch the rich API. On Android, serverless cold starts, or CLI tools this can matter. - **GraalVM native image**: all reflection needs registration, but kotlin-reflect's metadata machinery adds more to register and reason about; minimizing it eases native builds. - **Java interop**: Spring, Jackson (without the Kotlin module), JPA, etc. operate on `Class`/`Method`/`Field`. If you're plugging into them, Java handles are the currency. - **You simply don't need Kotlin semantics**: enumerating annotations, calling a no-default method, reading a `@JvmField` — Java reflection suffices. ## Why kotlin-reflect when you DO need it Nullability (`KType.isMarkedNullable`), default args (`callBy`/`isOptional`), unified properties (`memberProperties`), suspend detection (`isSuspend`), generics that survive (`typeOf<T>()`, `KType`, variance), Kotlin visibility/modality (`KVisibility`, `isOpen`). None of these are visible to Java reflection. ## Bridging between the two ```kotlin import kotlin.reflect.jvm.* val kClass = String::class val jClass: Class<String> = kClass.java // KClass -> Class val back = jClass.kotlin // Class -> KClass fun example(fn: kotlin.reflect.KFunction<*>) { val m = fn.javaMethod // KFunction -> Method? val ctor = fn.javaConstructor // KFunction -> Constructor? } // reverse direction // method.kotlinFunction, field.kotlinProperty ``` Property bridges: `KProperty.javaField`, `KProperty.javaGetter`, `KMutableProperty.javaSetter`. ## A pragmatic hybrid pattern 1. **Introspect** the model once with kotlin-reflect (nullability, defaults, property list) at startup. 2. **Resolve** to `java.lang.reflect` handles (`Method`, `Field`) and **cache** them. 3. **Invoke** on hot paths through the cached Java handles (or generated invokers), which avoids per-call kotlin-reflect overhead. This gives you Kotlin-faithful configuration with Java-speed execution. ## Gotchas - `.java`/`.kotlin` round-trips are cheap, but `javaMethod` can be `null` (e.g., for fake-override or intrinsic members). - `setAccessible`/`isAccessible` is governed by JPMS module openness regardless of which reflection API you use. - Mixing the two means you own correctness around name mangling and synthetic members on the Java side.
- Name two concrete reasons to avoid kotlin-reflect on Android or in a native image.It's an extra ~3MB dependency that increases app size, and its @Metadata parsing adds first-use startup cost; native images also require registering all that reflective surface.
- How do you go from a KClass to a java.lang.Class and back?kClass.java gives the Class; clazz.kotlin gives the KClass. They model the same runtime type from the two reflection systems.
saying these in an interview costs you the question
- Always reaching for kotlin-reflect even when Java reflection suffices
- Unaware kotlin-reflect is a separate dependency with startup cost
- Not knowing the .java/.kotlin and javaMethod/kotlinFunction bridges
- Ignoring native-image/Android constraints when choosing