Why Reified Types Matter

When I first moved a Java codebase to Kotlin, the biggest friction point wasn't syntax — it was the way generic type information disappears at runtime. Every time I wrote a helper like fun parse(json: String): T, the compiler reminded me that T is erased, forcing me to pass a Class or a TypeToken. That boilerplate adds noise and a surface for bugs.

The Problem: Type Erasure in Practice

Imagine a service layer that talks to three different REST endpoints, each returning a distinct data class: User, Order, and Product. The naïve approach looks like this:

// ❌ Doesn't compile — T is erased
fun  deserialize(json: String): T = Json.decodeFromString(json)

To make it work you end up with overloads or a when branch that checks T::class, which defeats the purpose of a single reusable function.

The Trick: Inline Functions with Reified Type Parameters

Kotlin lets you mark a type parameter as reified only on an inline function. The compiler then inlines the call site and substitutes the concrete type, so the generic information is available at runtime. The pattern is tiny but powerful:

// ✅ Works — T is reified
inline fun  deserialize(json: String): T {
    // The compiler knows the exact class of T here
    return Json.decodeFromString(json)
}

Because the function is inlined, the call deserialize(json) becomes essentially Json.decodeFromString(json) with no extra wrapper.

Real‑World Scenario: Centralised API Parsing

In a recent project we built a thin networking layer around kotlinx.serialization. All responses funneled through a single ApiClient that performed logging, error handling, and retry logic. The client exposed a generic request method:

class ApiClient(private val http: HttpClient) {
    // Centralised request logic
    suspend fun  request(
        method: HttpMethod,
        url: String,
        body: Any? = null
    ): Result = coroutineScope {
        val response = http.request(method, url) {
            if (body != null) setBody(Json.encodeToString(body))
        }
        return@coroutineScope when (response.status) {
            is HttpStatusCode.Success -> Result.success(deserialize(response.bodyAsText()))
            else -> Result.failure(ApiException(response.status, response.bodyAsText()))
        }
    }

    // The reified helper lives right next to the caller
    private inline fun  deserialize(json: String): T =
        Json.decodeFromString(json)
}

Now every feature module calls apiClient.request(GET, \