You only need to check an object's type and read its simple name. How do you do that without pulling in kotlin-reflect, and what exactly are the limits of ::class on stdlib alone?
answer
- ::class + simpleName/qualifiedName = free
- isInstance and is/when don't need kotlin-reflect
- .java bridges to java.lang.reflect
- Members/constructors/supertypes = need the JAR
- Identity is free; structure costs the artifact
basics
~10 sUse ::class with simpleName/qualifiedName, isInstance, or the is operator — these work without kotlin-reflect. You only hit the wall when you want members, constructors, or supertypes.
solid answer
~40 sThe `::class` operator and the `KClass` it returns are part of stdlib, so you can do lightweight type work with no extra dependency: `obj::class` / `Type::class`, `simpleName`, `qualifiedName`, `isInstance(x)`, equality/identity comparison of KClass instances, and `.java` to drop into plain `java.lang.reflect`. Idiomatic `is`/`as` checks and `when (x) { is Foo -> ... }` don't use reflection at all — they compile to `instanceof`/`checkcast`. The wall appears the moment you touch the structural API in `kotlin.reflect.full`: `memberProperties`, `declaredFunctions`, `constructors`/`primaryConstructor`, `createInstance()`, `supertypes`, `visibility`, `isData`/`isSealed`, or reflective `.call`/`.get`. Those require kotlin-reflect and throw `KotlinReflectionNotSupportedError` if it's absent. Rule of thumb: identity and naming are free; structure and invocation cost the JAR.
code
kotlin · 9 lines// Stdlib-only type handling — no kotlin-reflect required
fun handlerFor(x: Any): String = when (x) {
is Int -> "int:${x}"
is String -> "str:${x.length}"
else -> x::class.qualifiedName ?: "<anon>"
}
val cls = listOf(1).first()::class // KClass<Int>, free
println(cls.simpleName) // "Int"go deeper
Uses is/when for type checks but is unsure which APIs need the library.
Draws the line: naming/identity/isInstance free, structure/invocation needs kotlin-reflect.
Knows the .java bridge and what Kotlin metadata is lost when dropping to Java reflection.
Designs APIs to stay on the cheap stdlib path and avoids the artifact in size-critical modules by policy.
## What's free on stdlib (no kotlin-reflect) The `::class` literal returns a `KClass<T>`. A useful slice of it works with stdlib only: - **Get the KClass**: `value::class` (runtime class of the instance) or `Foo::class` (the literal type). - **Names**: `simpleName`, `qualifiedName`. - **Type tests**: `kclass.isInstance(obj)`. - **Identity**: comparing two `KClass` values with `==`. - **Java bridge**: `kclass.java` gives a `java.lang.Class`, from which you can use ordinary Java reflection (`java.lang.reflect`) — that is a *different* library and doesn't need kotlin-reflect. Separately, the language operators don't touch reflection at all: ```kotlin when (shape) { is Circle -> shape.radius // compiles to instanceof + checkcast is Square -> shape.side } val ok = obj is User // no reflection, no kotlin-reflect ``` ## A concrete stdlib-only example ```kotlin fun describe(x: Any): String { val k = x::class return "${k.simpleName} (java=${k.java.name}) isString=${String::class.isInstance(x)}" } // describe("hi") -> "String (java=java.lang.String) isString=true" ``` None of the above requires kotlin-reflect. ## Where the wall is (needs kotlin-reflect) Everything *structural* or *invocational* lives behind kotlin-reflect, mostly under `kotlin.reflect.full`: - Members: `memberProperties`, `declaredMemberProperties`, `memberFunctions`, `declaredFunctions`. - Construction: `constructors`, `primaryConstructor`, `createInstance()`. - Hierarchy & modifiers: `supertypes`, `allSuperclasses`, `visibility`, `isData`, `isSealed`, `isAbstract`. - Reflective invocation: `KFunction.call(...)`, `KProperty.getter.call(...)`, `KMutableProperty.setter.call(...)`. Touch any of these without the artifact and the runtime throws `KotlinReflectionNotSupportedError`. ## Decision rule - Need **type identity, naming, or instance checks**? → `::class` / `is` / `isInstance` — **no dependency**. - Need to **enumerate or invoke members, read structure, or construct via reflection**? → add **kotlin-reflect**. - Need Java-level structure but want to avoid kotlin-reflect? → bridge through `.java` and use `java.lang.reflect` (note: it won't see Kotlin-specific concepts like default args, nullability, or properties as Kotlin sees them).
- Does value::class need kotlin-reflect at runtime?No. Getting the KClass and reading simpleName/qualifiedName/isInstance is part of stdlib; only the kotlin.reflect.full structural API needs the artifact.
- If you bridge via .java to java.lang.reflect, what Kotlin info do you lose?Kotlin-specific concepts: nullability, default parameter values, properties (vs getters/setters), inline/suspend semantics, and visibility nuances — Java reflection sees only the JVM-level view.
saying these in an interview costs you the question
- Thinking 'is'/'when (is)' checks pull in kotlin-reflect
- Believing memberProperties works on stdlib
- Not knowing .java bridges to plain Java reflection
- Adding kotlin-reflect just to read simpleName
- Assuming java.lang.reflect sees Kotlin nullability/defaults