Blog Infos
Author
Published
Topics
, , , ,
Published

I’ve been working with Kotlin for a few years now, and one feature that really changed how I write code is value classes. They’re not talked about enough, but they solve a real problem we all face: how do you make your code safer without making it slower?

Let me show you what I mean.

Photo by Louis Tsai on Unsplash

The Problem We All Have

Picture this: you’re building an app that handles user accounts. You have user IDs, product IDs, order IDs everywhere. All of them are just integers, but they mean different things.

fun processOrder(userId: Int, productId: Int, orderId: Int) {
    // What if someone passes these in the wrong order?
    // The compiler won't catch it!
}

// This compiles fine but is totally wrong
processOrder(productId = 123, userId = 456, orderId = 789)

See the problem? The compiler can’t help us here. All three parameters are integers, so any order works. But in reality, mixing them up breaks everything.

The Old Way: Regular Classes

Before value classes, we’d solve this with wrapper classes:

data class UserId(val value: Int)
data class ProductId(val value: Int) 
data class OrderId(val value: Int)

fun processOrder(userId: UserId, productId: ProductId, orderId: OrderId) {
    println("Processing order ${orderId.value} for user ${userId.value}")
}
// Now this won't compile - much better!
// processOrder(ProductId(123), UserId(456), OrderId(789)) // Wrong order = compile error

This works great for safety. But there’s a catch — performance.

Every time you create UserId(123), you’re actually creating a real object in memory. That object has overhead. If you’re doing this thousands of times in a loop, it adds up.

I remember profiling an app where we wrapped everything in data classes like this. The garbage collector was going crazy cleaning up all these tiny wrapper objects.

Enter Value Classes

Kotlin introduced us to value classes. They give you the safety of wrapper classes with zero runtime overhead.

Here’s the magic:

@JvmInline
value class UserId(val value: Int)
@JvmInline  
value class ProductId(val value: Int)
@JvmInline
value class OrderId(val value: Int)

fun processOrder(userId: UserId, productId: ProductId, orderId: OrderId) {
    println("Processing order ${orderId.value} for user ${userId.value}")
}

At compile time, this looks exactly like the regular class version. You get type safety — the compiler will yell if you mix up the parameters.

But at runtime? The Kotlin compiler is smart. It sees these value classes and thinks “I can optimize this away.” Your UserId(123) becomes just 123 in the final bytecode.

Real Example: Money Handling

Let me show you a practical example. I was building a payment system and needed to handle different currencies:

@JvmInline
value class USD(val cents: Int)
@JvmInline  
value class EUR(val cents: Int)
@JvmInline
value class GBP(val pence: Int)

fun chargeCreditCard(amount: USD) {
    println("Charging ${amount.cents} cents USD")
}
fun convertToEur(usd: USD): EUR {
    // Pretend exchange rate logic here  
    return EUR((usd.cents * 0.85).toInt())
}

Without value classes, I might have written:

fun chargeCreditCard(amountInCents: Int, currency: String) {
    // Hope nobody passes the wrong currency string!
    println("Charging $amountInCents cents $currency")
}

But that’s dangerous. Someone could easily pass euros where dollars are expected, or misspell “USD” as “usd”.

With value classes, this becomes impossible:

val payment = USD(1500) // $15.00
chargeCreditCard(payment)

val euroAmount = EUR(1200) 
// chargeCreditCard(euroAmount) // Won't compile!
How Kotlin Handles This Internally

Here’s what’s happening under the hood. When you write:

@JvmInline
value class UserId(val value: Int)

fun getUser(id: UserId): String {
    return "User with ID: ${id.value}"
}
val user = getUser(UserId(42))

The Kotlin compiler transforms this into something like:

// What the JVM actually sees:
fun getUser(id: Int): String {  // UserId disappeared!
    return "User with ID: $id"
}

val user = getUser(42)  // No object creation!

The value class completely vanishes at runtime. You get the same performance as if you’d used raw integers, but with compile-time safety.

There are some cases where Kotlin can’t value the class, like when you use it as a generic type parameter or store it in a collection. In those cases, it falls back to creating real objects. But for most everyday use, it’s optimised away.

More Practical Examples
API Tokens

 

@JvmInline  
value class ApiToken(val value: String)

@JvmInline
value class RefreshToken(val value: String)

fun authenticateRequest(token: ApiToken): Boolean {
    // Can't accidentally pass a refresh token here
    return token.value.startsWith("api_")
}

 

Database IDs

 

@JvmInline
value class CustomerId(val id: Long)

@JvmInline  
value class InvoiceId(val id: Long)

fun findInvoicesForCustomer(customerId: CustomerId): List<InvoiceId> {
    // Type-safe database queries
    return database.query("SELECT id FROM invoices WHERE customer_id = ?", customerId.id)
        .map { InvoiceId(it.getLong("id")) }
}

 

Units of Measurement

 

@JvmInline
value class Meters(val value: Double)

@JvmInline
value class Feet(val value: Double)  

fun calculateArea(length: Meters, width: Meters): Meters {
    return Meters(length.value * width.value)
}
// Can't mix feet and meters by accident
val roomLength = Meters(5.0)
val roomWidth = Meters(3.0) 
val area = calculateArea(roomLength, roomWidth)

 

When NOT to Use Value Classes

They’re not perfect for everything. Here are cases where I don’t use them:

Multiple properties: Value classes can only wrap a SINGLE value. You literally can’t do this:

// This won't even compile!
@JvmInline
value class User(val id: Int, val name: String, val email: String) // ERROR!

But you SHOULD use value classes for the individual parts:

@JvmInline
value class UserId(val value: Int)

@JvmInline  
value class UserName(val value: String)

@JvmInline
value class Email(val value: String)

// Then use them in your regular class
data class User(
    val id: UserId, 
    val name: UserName, 
    val email: Email
) {
    fun sendEmail() { /* complex logic */ }
    fun validateData() { /* more logic */ }
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

This way, you get type safety for each individual field, but you can still have a regular class with multiple properties and methods.

Collections of them: When you put value classes in lists or maps, they often can’t be valued anymore.

Inheritance: Value classes can’t extend other classes or be extended.

The Bottom Line

Value classes solve a real problem: they let you write safer code without sacrificing performance. They’re especially useful for:

  • Wrapping primitive types (IDs, tokens, measurements)
  • Preventing parameter mix-ups
  • Making APIs harder to misuse
  • Adding meaning to simple values

I’ve started using them everywhere I used to create simple wrapper classes. My code is safer, and the performance is identical to using raw primitives.

Try them out in your next project. Once you start using value classes, you’ll wonder how you lived without them.

For any questions or queries, let’s connect on LinkedIn

https://www.linkedin.com/in/devbaljeet/

 

This article was previously published on proandroiddev.com

Menu