skip to content

How Kotlin Does Reflection & Metaprogramming

Kotlin exposes its own declarations through KClass and KCallable, carries metadata via annotations with use-site targets, and generates serializers with a compiler plugin rather than reflection. Knowing which of these costs you a runtime dependency is the practical thread.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is a KClass in Kotlin reflection, how do you obtain one, and how does it differ from java.lang.Class?

level: juniorimportance: must knowfreq 55%

answer

  1. ::class gives KClass, ::class.java gives Class
  2. Bound (instance::class) vs literal (Type::class)
  3. .java and .kotlin bridge the two worlds
  4. isData/isSealed/objectInstance are Kotlin-only
  5. Full introspection needs kotlin-reflect on classpath

basics

~20 s

A KClass is Kotlin's runtime handle to a class. You get it with String::class for a type or instance::class for an object. It exposes Kotlin features like properties and nullability; java.lang.Class only knows the Java view.

solid answer

~40 s

KClass<T> is the entry point of Kotlin reflection, representing a class at runtime. You obtain one with a class literal (String::class) or from an instance (myObj::class). KClass exposes Kotlin-specific members: members (KCallable), declaredMemberProperties, constructors, isData, isSealed, objectInstance, plus visibility and nullability info. java.lang.Class is the JVM view and misses Kotlin concepts (no notion of data/sealed, properties, or nullable types). You bridge between them with .java (KClass to Class) and .kotlin (Class to KClass). Basic class references like String::class are bound metadata and need only the runtime; richer introspection (members, parameters) requires the separate kotlin-reflect artifact on the classpath, otherwise calls throw KotlinReflectionNotSupportedError.

code

kotlin · 13 lines
kotlin
import kotlin.reflect.KClass
import kotlin.reflect.full.declaredMemberProperties

data class User(val id: Long, val name: String)

fun main() {
    val kc: KClass<User> = User::class
    println(kc.isData)                  // true
    println(kc.simpleName)              // User
    kc.declaredMemberProperties.forEach { println(it.name) }  // id, name
    val jc: Class<User> = kc.java       // bridge to java.lang.Class
    println(jc.name)                    // fully qualified Java name
}

go deeper

for a junior

Knows ::class returns a KClass and ::class.java returns the Java Class.

for a middle

Explains bound vs literal references and the .java/.kotlin bridge plus Kotlin-only flags.

for a senior

Discusses the kotlin-reflect dependency boundary and the performance/cost of reflective access.

for a principal

Reasons about when to avoid reflection entirely (startup cost, GraalVM native-image, security) and prefer compile-time alternatives.

## What is KClass `KClass<T>` is the central type of the **Kotlin reflection** API. It is a runtime representation of a class or interface — analogous to Java's `java.lang.Class`, but it understands Kotlin-only concepts. ## Obtaining a KClass There are two forms of the `::class` operator: - **Bound class reference** — from an instance: `instance::class` gives the *runtime* class of that object. - **Class literal** — from a type: `String::class` gives the class for that declared type. ```kotlin val a: KClass<String> = String::class // class literal val s: Any = "hi" val b: KClass<out Any> = s::class // bound reference -> String::class at runtime ``` ## What KClass exposes - `simpleName`, `qualifiedName` - `members` / `declaredMemberProperties` / `declaredMemberFunctions` — collections of `KCallable` - `constructors`, `primaryConstructor` - Kotlin flags: `isData`, `isSealed`, `isAbstract`, `isCompanion`, `isInner`, `isFun` - `objectInstance` — the singleton for an `object` declaration - `sealedSubclasses`, `supertypes`, `visibility` ## Difference from java.lang.Class `java.lang.Class` is the JVM-level view. It has no concept of Kotlin data/sealed classes, properties (it sees getter/setter methods), nullability, or `object` singletons. You convert between the two: ```kotlin val jc: Class<String> = String::class.java // KClass -> Class val kc: KClass<String> = jc.kotlin // Class -> KClass ``` ## kotlin-reflect dependency Simple references compile into lightweight metadata, but full introspection (`members`, `primaryConstructor`, calling things) needs the **`kotlin-reflect`** library on the classpath. Without it, those calls fail with `KotlinReflectionNotSupportedError`. Reflection is also slower than direct calls, so use it sparingly.

  • Why might String::class.members throw at runtime even though it compiles fine?
    Full member introspection requires the kotlin-reflect artifact on the classpath; without it the call throws KotlinReflectionNotSupportedError.
  • What's the difference between String::class and a value of type KClass<out Any> from someAny::class?
    The literal is statically String::class; the bound reference resolves to the object's actual runtime class, so its static type is KClass<out Any>.

KClass is the Kotlin-language passport for a type; java.lang.Class is the older JVM ID card that omits the Kotlin-specific stamps.

saying these in an interview costs you the question

  • Thinking KClass and java.lang.Class are the same object
  • Claiming reflection works fully with no extra dependency
  • Confusing ::class (KClass) with ::class.java (Class)
  • Saying java.lang.Class knows about data/sealed classes
  • Not knowing instance::class returns the runtime class

context

open as a page

How do annotations and use-site targets work in Kotlin, and why are targets like @get:, @field:, @param: necessary?

level: middleimportance: must knowfreq 50%

basics

~20 s

A single Kotlin property compiles into several JVM elements: a field, a getter, a constructor parameter. Use-site targets like @field: or @get: tell the compiler which one the annotation lands on, since each may need it.

open as a page

Explain the KCallable hierarchy — KCallable, KFunction, KProperty — and how you call or read members reflectively.

level: middleimportance: should knowfreq 45%

basics

~10 s

KCallable is the common type for anything you can call: functions and properties. KFunction is invoked with call(args). KProperty has a getter you read with get(receiver); mutable ones (KMutableProperty) also have a setter.

open as a page

How does Kotlin represent generic types at runtime via KType, and how do typeOf<T>() and reified parameters overcome JVM type erasure?

level: seniorimportance: should knowfreq 30%

basics

~10 s

The JVM erases generics, so List<String> looks like List at runtime. Kotlin's typeOf<T>() captures the full type, and reified type parameters in inline functions keep T available so you can inspect or use it.

open as a page

How does the kotlinx.serialization @Serializable plugin generate serializers at compile time, and why is that different from reflection-based serialization?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The @Serializable compiler plugin writes a serializer for your class while it compiles, so no runtime reflection is needed. Reflection-based tools instead inspect the class at runtime, which is slower and harder to use on native or minified targets.

open as a page