skip to content

Why do JVM frameworks like Jackson, Spring, or JPA require you to pass MyType::class.java instead of MyType::class?

level: middleimportance: must knowfreq 65%

answer

  1. Java APIs typed against Class<T>, not KClass
  2. KClass is NOT a subtype of Class
  3. .java token == Java's MyType.class
  4. raw Class -> .kotlin for data/sealed/nullability info
  5. bare Class can't carry generics (erasure) -> TypeReference

basics

~10 s

Those frameworks are written in Java and only understand Java's Class type. Kotlin's KClass is a different type, so you convert it with .java before handing it over.

solid answer

~30 s

Frameworks such as Jackson, Spring, Hibernate/JPA, and the JDK itself were written against `java.lang.Class<T>` — their method signatures literally take `Class<T>` parameters (e.g. `ObjectMapper.readValue(String, Class<T>)`, `ApplicationContext.getBean(Class<T>)`). Kotlin's `KClass<T>` is a distinct type and is not assignable to `Class<T>`, so you must call the `.java` extension property to get the `java.lang.Class<T>` token the API expects: `mapper.readValue(json, Dto::class.java)`. Conversely, when such a framework hands you a raw `Class` (e.g. in a `BeanPostProcessor` or annotation scanner) and you want Kotlin-aware reflection, you call `.kotlin` to obtain a `KClass`. The conversion is cheap — it just exposes the already-loaded type handle the other way.

code

kotlin · 9 lines
kotlin
// Jackson needs java.lang.Class, so convert with .java
val mapper = jacksonObjectMapper()
val dto: UserDto = mapper.readValue(json, UserDto::class.java)

// Spring bean lookup
val svc = ctx.getBean(UserService::class.java)

// Framework handed us a raw Class -> go Kotlin-aware
fun isKotlinDataClass(raw: Class<*>) = raw.kotlin.isData

go deeper

for a junior

Knows to add .java when a Java framework complains about the type; can fix a readValue call.

for a middle

Explains that Java signatures take Class<T> and KClass isn't assignable, and recalls the reverse .kotlin use.

for a senior

Adds the erasure caveat (TypeReference for generics) and uses .kotlin to extract Kotlin-only metadata.

for a principal

Discusses why the two handles stay distinct (interop boundary, Kotlin-only semantics) and the design trade-offs of marking conversions explicitly.

## The root cause: Java APIs speak java.lang.Class The vast JVM ecosystem (Jackson, Gson, Spring, Hibernate/JPA, JDBC, servlet APIs, the JDK reflection package) predates Kotlin and is compiled against **`java.lang.Class<T>`**. Their signatures are fixed, for example: ```java // Jackson <T> T readValue(String content, Class<T> valueType) // Spring <T> T getBean(Class<T> requiredType) ``` Kotlin's **`KClass<T>`** is a *separate* interface in `kotlin.reflect`. It is **not** a subtype of `Class<T>`, so you cannot pass a `KClass` where a `Class` is required — the types are incompatible. ## The fix: the .java bridge The stdlib extension property `KClass<T>.java` returns the matching `java.lang.Class<T>`, which *is* exactly what these APIs want: ```kotlin val dto: Dto = mapper.readValue(json, Dto::class.java) val bean = ctx.getBean(MyService::class.java) val clazz: Class<*> = entityManager.metamodel .entity(User::class.java).javaType ``` Note: `MyType::class.java` is identical to what Java code would write as `MyType.class`, so it slots into Java signatures perfectly. ## Going the other direction When a framework gives you a raw `Class` (annotation processors, `BeanPostProcessor`, deserializers reflecting over fields), convert to `KClass` for Kotlin-aware features (nullability, `data` class detection, properties, companions): ```kotlin fun describe(raw: Class<*>) { val k: KClass<*> = raw.kotlin println(k.isData) // Kotlin-only info println(k.simpleName) } ``` ## Why not just make KClass assignable to Class? Because `KClass` carries Kotlin-only semantics that have no Java equivalent (e.g. it can model `data`/`sealed`/`object` declarations and Kotlin visibility). Keeping them distinct avoids leaking Kotlin concepts into Java's contract and lets each side stay precise. The explicit `.java`/`.kotlin` conversion marks the **interop boundary**. ## Generics caveat A `Class<T>` token only carries the **raw** type — it cannot express `List<String>` vs `List<Int>` due to JVM type erasure. For generic deserialization, Jackson and friends use `TypeReference`/`TypeToken`, not a bare `Class`. So `.java` solves the *raw type* handoff, but generic payloads still need those parameterized-type helpers (or Kotlin's `typeOf()`).

  • Can you pass a KClass directly to ObjectMapper.readValue?
    No. Its signature takes java.lang.Class<T>; you must call .java first since KClass isn't assignable to Class.
  • How would you deserialize a List<UserDto> with Jackson given erasure?
    Use a TypeReference<List<UserDto>>() (or constructParametricType); a bare Class token loses the generic parameter.

KClass is a metric wrench; the Java framework only has imperial bolts — .java is the adapter that fits.

saying these in an interview costs you the question

  • Claiming you can pass MyType::class to a Java API directly
  • Saying KClass extends/implements Class
  • Thinking .java preserves generic type arguments
  • Not knowing the reverse .kotlin direction exists
  • Confusing this with needing kotlin-reflect for the conversion itself

context