Question

How reified Works in Kotlin Generics

kotlingenericskotlin-reified-type-parameters

Question

I'm trying to understand the purpose of the reified keyword in Kotlin. It seems to allow reflection-like operations on generic types.

However, when I remove it, my code still appears to work fine. In what situations does reified actually make a real difference?

Short Answer

By the end of this page, you will understand what reified means in Kotlin, why it only works with inline functions, and when it is necessary instead of a normal generic type parameter. You will also see the difference between generic type information that is erased at runtime and type information that becomes available through reification.

Concept

In Kotlin, generic type parameters such as T are usually erased at runtime. This is called type erasure.

That means inside a normal generic function, the program often does not know the actual type that was used for T once the code is running.

For example, in a regular function like this:

fun <T> printType(value: T) {
    // You cannot reliably use T::class here
}

T is mainly useful at compile time. The compiler checks types for safety, but the runtime usually does not keep that generic type information.

reified changes this behavior for inline functions.

When you write:

inline fun <reified T> example() {
    println(T::class)
}

Kotlin replaces the function call with the actual function body at compile time. Because of that, the compiler can substitute the real type argument directly into the generated code.

So instead of treating T as an unknown erased type, the function can act as if the actual type is available at runtime.

This matters when you want to do things like:

  • access T::class
  • check value is T
  • cast with value as? T
  • call APIs that need a Class or KClass derived from T

Without reified, you usually have to pass the type explicitly:

fun <T : Any> example(type: kotlin.reflect.KClass<T>) {
    println(type)
}

example(String::class)

With reified, Kotlin can often infer and provide that type for you:

inline fun <reified T : Any> example() {
    println(T::class)
}

example<String>()

Why this matters in real programming:

  • It makes APIs cleaner.
  • It reduces boilerplate like passing MyType::class manually.
  • It is very common in utility functions for JSON parsing, dependency lookup, filtering, and type-safe helpers.

Important limitation:

  • reified only works on type parameters of inline functions.
  • It does not magically preserve all generic information everywhere.
  • It helps because the compiler inserts the concrete type at each call site.

Mental Model

Think of a normal generic type T as a label written in pencil on a box.

At compile time, the label helps you organize things correctly. But at runtime, the label gets erased, so the program no longer clearly sees what was written.

A reified type is like turning that pencil label into a printed sticker right when the box is handed over.

Because the function is inline, Kotlin copies the function body directly to the call site and can replace T with the real type, such as String or Int.

So:

  • normal generic T = the runtime often forgets the exact type
  • reified T = the actual type is written into the generated code at the call site

That is why reified is useful when the code needs to inspect or check the type while running.

Take Quiz

Syntax and Examples

Basic syntax

reified must be used with an inline function:

inline fun <reified T> showType() {
    println(T::class)
}

Call it like this:

showType<String>()
showType<Int>()

Example: checking a type

inline fun <reified T> isOfType(value: Any): Boolean {
    return value is T
}

fun main() {
    println(isOfType<String>("hello")) // true
    println(isOfType<String>(123))     // false
}

Why this is useful

Without reified, this does not work:

fun <T> isOfType(value: Any): Boolean {
    // return value is T
    return false
}

The compiler rejects it because T is erased.

Example: getting a class reference

inline fun <reified T : Any> printClassName() {
    println(T::class.simpleName)
}

fun main() {
    printClassName<String>() // String
    printClassName<List<Int>>() // List
}

Equivalent non-reified approach

If you do not use reified, you often pass the class explicitly:

fun <T : Any> printClassName(type: kotlin.reflect.KClass<T>) {
    println(type.simpleName)
}

fun main() {
    printClassName(String::class)
}

This version still works, but it is more verbose.

When leaving out reified still works

If your generic function never needs runtime access to the type, reified is unnecessary.

For example:

fun <T> firstItem(items: List<T>): T? {
    return items.firstOrNull()
}

This works perfectly without reified because the function only moves values around using compile-time type safety. It does not inspect T at runtime.

Step by Step Execution

Consider this code:

inline fun <reified T> matchesType(value: Any): Boolean {
    return value is T
}

fun main() {
    val result1 = matchesType<String>("Kotlin")
    val result2 = matchesType<String>(42)

    println(result1)
    println(result2)
}

What happens step by step

  1. The compiler sees that matchesType is marked inline.
  2. Instead of generating one shared generic function call, Kotlin copies the function body into each call site.
  3. For matchesType<String>("Kotlin"), T is replaced with String.
  4. The generated logic becomes effectively similar to:
val result1 = "Kotlin" is String
  1. For matchesType<String>(42), the generated logic becomes effectively similar to:
val result2 = 42 is String
  1. The first expression is true.
  2. The second expression is false.
  3. The program prints:
true
false

Compare that with a normal generic function

If the function were not inline and not reified, Kotlin would only know there is some T, but not which concrete type to check at runtime.

That is the key difference: reified turns the type argument into something usable in the generated runtime code.

Real World Use Cases

reified is most useful when a helper function needs to know the concrete type at runtime.

1. JSON parsing helpers

Libraries often need a target type.

inline fun <reified T> parseJson(json: String): T {
    // Example idea: a JSON library uses T's type here
    throw NotImplementedError("Demo only")
}

This is cleaner than forcing callers to pass T::class every time.

2. Dependency injection or service lookup

inline fun <reified T> resolve(): T {
    throw NotImplementedError("Demo only")
}

A container can look up an object by type.

3. Filtering collections by type

Kotlin standard library uses this idea:

val mixed: List<Any> = listOf("a", 1, "b", 2)
val strings = mixed.filterIsInstance<String>()
println(strings) // [a, b]

This works because the function needs runtime type information.

4. Android and framework utilities

Developers use reified helpers for:

  • creating intents
  • retrieving fragments
  • reading bundles
  • locating serializers
  • registering handlers by type

5. Safer casting helpers

inline fun <reified T> castOrNull(value: Any): T? {
    return value as? T
}

This is convenient when converting loosely typed data into strongly typed values.

Take Quiz

Real Codebase Usage

In real Kotlin projects, reified usually appears in small utility functions that make APIs easier to call.

Common patterns

Guard clauses based on type

inline fun <reified T> requireType(value: Any) {
    require(value is T) { "Expected ${T::class.simpleName}" }
}

This is useful for validation and defensive programming.

Wrapper functions around libraries

A library may require a Class<T> or KClass<T>. Teams often write a reified wrapper:

fun <T : Any> loadConfig(type: kotlin.reflect.KClass<T>): T {
    throw NotImplementedError("Demo only")
}

inline fun <reified T : Any> loadConfig(): T {
    return loadConfig(T::class)
}

This keeps the core implementation explicit while giving callers a cleaner API.

Type-safe lookups

class Container {
    private val services = mutableMapOf<Class<*>, Any>()

    fun <T : Any> register(type: Class<T>, instance: T) {
        services[type] = instance
    }

    inline fun <reified T : Any> get(): T? {
        return services[T::class.java] as? T
    }
}

Filtering mixed collections

val events: List<Any> = listOf("start", 1, true, "stop")
val textEvents = events.filterIsInstance<String>()

Why teams use it carefully

Although reified is convenient, developers do not use it everywhere.

They usually prefer it when:

  • runtime type checks are actually needed
  • a wrapper around reflection or class tokens improves readability
  • the function is small enough to be safely inlined

They avoid it when:

  • the function does not need runtime type information
  • inlining would duplicate too much code
  • passing a Class or KClass explicitly is clearer

Common Mistakes

1. Thinking reified is needed for all generics

This is the most common misunderstanding.

Broken idea:

inline fun <reified T> identity(value: T): T {
    return value
}

This works, but reified adds no benefit here because the function never uses T at runtime.

Use reified only when you need runtime access to the type.

2. Forgetting that reified requires inline

Broken code:

fun <reified T> printType() {
    println(T::class)
}

This will not compile.

Correct version:

inline fun <reified T> printType() {
    println(T::class)
}

3. Assuming reified preserves all nested generic details

Example:

inline fun <reified T> show() {
    println(T::class)
}

show<List<String>>()

You may see List as the runtime class, not all nested generic details in the way beginners often expect.

reified helps with the top-level type argument used in the inline function, but generic type handling still has limits.

4. Confusing compile-time type safety with runtime type information

This works without reified:

fun <T> echo(value: T): T = value

This does not need runtime inspection.

This needs reified:

inline fun <reified T> isType(value: Any) = value is T

Ask yourself: Do I need to inspect the type while the program is running?

If not, reified is probably unnecessary.

5. Overusing reified wrappers

Sometimes a direct parameter is simpler and clearer:

fun <T : Any> create(type: kotlin.reflect.KClass<T>): T {
    throw NotImplementedError("Demo only")
}

A reified wrapper can be nice, but it should improve the API, not hide important behavior.

Comparisons

ConceptWhat it doesRuntime type accessTypical use
Normal generic fun <T>Uses a type parameter for compile-time safetyUsually noData processing, collections, reusable functions
inline fun <reified T>Makes the concrete type available in inline-generated codeYes, for many type operationsType checks, casts, class lookup, wrappers around reflection
Passing KClass<T> manuallyCaller provides the type explicitlyYesConstructors, factories, reflection-heavy APIs

reified vs explicit class parameter

fun <T : Any> create(type: kotlin.reflect.KClass<T>): T {
    throw NotImplementedError("Demo only")
}

inline fun <reified T : Any> create(): T {
    return create(T::class)
}

Trade-off

  • Explicit KClass parameter

    • clearer about where the runtime type comes from
    • works without inline
    • useful when the type must be passed around
  • reified

    • cleaner call site
    • less boilerplate
    • only available in inline functions

reified vs reflection

reified is not the same as full reflection.

  • reified makes the actual type argument available in certain runtime expressions
  • reflection is a larger mechanism for inspecting classes, properties, annotations, and more

reified often helps you reach type information more conveniently, but it is not itself a full reflection system.

Take Quiz

Cheat Sheet

Quick rules

  • Generic types in Kotlin are usually erased at runtime.
  • Use reified when you need the actual type T while the program is running.
  • reified only works on inline functions.
  • Common uses:
    • T::class
    • T::class.java
    • value is T
    • value as? T

Core syntax

inline fun <reified T> example(value: Any): Boolean {
    return value is T
}

Without reified

Pass the type explicitly:

fun <T : Any> example(type: kotlin.reflect.KClass<T>) {
    println(type.simpleName)
}

Use reified when

  • you want cleaner APIs around class-based operations
  • you need runtime checks involving T
  • you are building utility functions

Do not use reified when

  • the function only returns or forwards T
  • no runtime type inspection is needed
  • inlining would add unnecessary complexity

Remember

fun <T> ok(value: T): T = value
inline fun <reified T> needed(value: Any) = value is T

Limitation

reified helps with runtime access to the type parameter in an inline function, but it does not remove all generic type erasure limitations in every situation.

FAQ

Why does my Kotlin generic function work without reified?

Because many generic functions only need compile-time type safety, not runtime type inspection. If you are not doing is T, as? T, or T::class, reified may not be needed.

Why does reified require inline in Kotlin?

Because Kotlin makes reified work by copying the function body to each call site and replacing T with the actual type used there.

Can I use reified in a normal class method without inline?

No. A type parameter can only be reified in an inline function.

Is reified the same as reflection?

No. It is a way to preserve access to a generic type argument in inline functions. It can help with reflection-like tasks, but it is not full reflection by itself.

When should I use reified instead of passing Class or KClass?

Use reified when you want a cleaner helper function and the function can reasonably be inline. Use explicit class parameters when the type must be passed around or when inlining is not appropriate.

Can reified fully preserve List<String> at runtime?

Not in the simple sense many beginners expect. It helps with access to the type parameter, but nested generic details still have runtime limitations.

Does reified make code slower?

Not necessarily, but inline functions duplicate code at call sites. This is usually fine for small utility functions, which is where reified is most commonly used.

Take Quiz

Mini Project

Description

Build a small type-filtering utility for a mixed list. This demonstrates exactly why reified is useful: the function needs to keep only items of a requested type at runtime.

Goal

Create a reusable Kotlin function that returns only values of a specific type from a mixed collection.

Requirements

  • Create an inline function with a reified type parameter.
  • Accept a List<Any> as input.
  • Return a new list containing only items of type T.
  • Demonstrate the function with at least two different target types.
  • Print the results clearly.
Take Quiz

Keep learning

Related questions

View more