skip to content

Explain `simpleName` vs `qualifiedName` on a KClass, when each can be null, and how they behave for nested, local, and anonymous classes.

level: seniorimportance: nice to knowfreq 30%

answer

  1. simpleName = short, qualifiedName = package + dotted path
  2. both nullable
  3. local class: simpleName ok, qualifiedName null
  4. anonymous object: both null
  5. stdlib provides names; no kotlin-reflect needed

basics

~10 s

simpleName is the short class name; qualifiedName is the full dotted name including package. Both are nullable: anonymous objects and local classes have no real name, so they return null.

solid answer

~40 s

On `KClass<T>`, `simpleName: String?` is the class's own short name (no package, no enclosing-class prefix), and `qualifiedName: String?` is the fully qualified dotted name (`com.acme.Outer.Inner`). Both are nullable. `simpleName` is null only for anonymous objects (no source name at all). `qualifiedName` is null in more cases: anonymous objects **and** local classes (declared inside a function) — because a local class has no stable, package-rooted path. For top-level and nested/inner classes both are present, with `qualifiedName` joining the enclosing classes by dots. These two properties are available from the lightweight stdlib `KClass` and do **not** require kotlin-reflect, unlike `members`, `objectInstance`, or `isSubclassOf`. Note `qualifiedName` uses Kotlin dotted notation, which differs from the JVM binary name (`Outer$Inner`) you would get via `.java.name`.

code

kotlin · 7 lines
kotlin
package com.acme
class Outer { inner class Inner }

fun main() {
    println(Outer.Inner::class.qualifiedName)  // com.acme.Outer.Inner (dotted)
    println(Outer.Inner::class.java.name)      // com.acme.Outer$Inner (JVM, $)
}

go deeper

for a junior

Knows simpleName is short and qualifiedName is the full name.

for a middle

Knows both are nullable and gives the anonymous-object case.

for a senior

States the full nullability matrix (local vs anonymous) and the dotted vs $ JVM-name distinction.

for a principal

Chooses naming strategy for serialization/diagnostics with null-safe fallbacks and avoids dependency creep by using stdlib-only name access where possible.

## The two name properties ```kotlin package com.acme class Outer { class Inner } Outer::class.simpleName // "Outer" Outer::class.qualifiedName // "com.acme.Outer" Outer.Inner::class.simpleName // "Inner" Outer.Inner::class.qualifiedName // "com.acme.Outer.Inner" (dotted, Kotlin-style) ``` - `simpleName` -> just the declared name of that class. - `qualifiedName` -> package + enclosing classes + name, joined with `.`. ## Nullability rules (the interview crux) | Kind | simpleName | qualifiedName | |------|-----------|---------------| | Top-level class | name | full dotted name | | Nested / inner class | name | full dotted name | | Local class (inside a function) | name | **null** | | Anonymous object (`object : Foo {}`) | **null** | **null** | Local classes have a name in source but no package-rooted qualified path, so `qualifiedName` is null while `simpleName` is the local name. Anonymous objects have neither. ```kotlin fun demo() { class Local println(Local::class.simpleName) // "Local" println(Local::class.qualifiedName) // null val anon = object {} println(anon::class.simpleName) // null println(anon::class.qualifiedName) // null } ``` ## Kotlin name vs JVM binary name `qualifiedName` is the Kotlin representation using dots between outer and nested classes. The JVM binary name (from `.java.name`) uses a `$` separator: `com.acme.Outer$Inner`. Don't conflate them — serialization keyed on one won't match the other. ## No kotlin-reflect needed These name properties (plus `isInstance` and identity/`equals`) are served by the minimal stdlib `KClass` implementation. The richer surface (`members`, `constructors`, `objectInstance`, `sealedSubclasses`, `isSubclassOf`) needs the `kotlin-reflect` artifact, which is why a lib that only needs names can avoid that dependency. ## Practical guidance - Logging/diagnostics: prefer `simpleName ?: "<anonymous>"`. - Stable identifiers (serialization discriminators): prefer `qualifiedName` but guard against null and pick a fallback for local/anonymous types. - Don't use `qualifiedName` where you actually need the JVM binary name (e.g. `Class.forName`).

  • Why is `qualifiedName` null for a local class but `simpleName` is not?
    A local class has a source name (so simpleName exists) but no stable package-rooted path, so it cannot form a qualified name.
  • Do you need kotlin-reflect to read these properties?
    No — `simpleName`/`qualifiedName` come from the lightweight stdlib KClass; only the richer reflection surface needs kotlin-reflect.

saying these in an interview costs you the question

  • Saying both names are always non-null
  • Claiming `qualifiedName` equals the JVM binary name (it uses dots, not `$`)
  • Thinking reading names requires kotlin-reflect
  • Confusing local-class and anonymous-object nullability behavior

context