Skip to main content

Kotlin Value Classes

Introduction​

Kotlin Value Classes (previously known as inline classes) are a powerful feature that lets you create type-safe wrappers around existing types without the runtime overhead of creating actual object instances. They help you design more type-safe APIs while maintaining the performance characteristics of the underlying primitive types.

In normal object-oriented programming, wrapping a primitive value in a class would incur memory allocation overhead. Value classes solve this problem by "inlining" the underlying value, eliminating the wrapper object at runtime in most cases.

Understanding Value Classes​

Basic Syntax​

To create a value class in Kotlin, use the value class declaration and specify exactly one property in the primary constructor:

kotlin
value class Password(val value: String)

This creates a type-safe wrapper around a String that represents a password. At compile time, you work with the Password type, but at runtime, in most cases, the compiler uses the underlying String directly.

How It Works​

Let's see how value classes behave compared to regular classes:

kotlin
// Regular class
class RegularPassword(val value: String)

// Value class
value class ValuePassword(val value: String)

fun main() {
// Using regular class
val regularPassword1 = RegularPassword("secret1")
val regularPassword2 = RegularPassword("secret1")

// Using value class
val valuePassword1 = ValuePassword("secret1")
val valuePassword2 = ValuePassword("secret1")

// Equality checks
println(regularPassword1 == regularPassword2) // false (different instances)
println(valuePassword1 == valuePassword2) // true (compared by value)

// Type information
println(regularPassword1.javaClass) // RegularPassword
println(valuePassword1.javaClass) // Usually optimized away
}

The key difference is that regular classes create distinct objects in memory, while value classes typically don't create any object at runtime - they're "inlined" to their underlying type.

Benefits of Value Classes​

1. Type Safety Without Overhead​

Value classes allow you to create domain-specific types without performance penalties:

kotlin
value class UserId(val id: Int)
value class ProductId(val id: Int)

// Functions specific to each type
fun fetchUser(userId: UserId) {
// Works with UserId only
println("Fetching user ${userId.id}")
}

fun fetchProduct(productId: ProductId) {
// Works with ProductId only
println("Fetching product ${productId.id}")
}

fun main() {
val userId = UserId(1001)
val productId = ProductId(2002)

fetchUser(userId) // Works
// fetchUser(productId) // Compile error: Type mismatch
// fetchUser(1001) // Compile error: Type mismatch

fetchProduct(productId) // Works
}

Without value classes, you would either:

  • Use primitive types (losing type safety)
  • Create full classes (adding runtime overhead)

2. Clearer API Design​

Value classes make your code's intent more obvious:

kotlin
// Without value classes - which parameter means what?
fun updateUserSettings(timeout: Int, retries: Int, maxConnections: Int) {
// Implementation
}

// With value classes - much clearer intent
value class Timeout(val seconds: Int)
value class RetryCount(val count: Int)
value class ConnectionLimit(val max: Int)

fun updateUserSettings(timeout: Timeout, retries: RetryCount, maxConnections: ConnectionLimit) {
// Implementation is self-documenting
println("Setting timeout to ${timeout.seconds}s")
println("Setting retries to ${retries.count}")
println("Setting max connections to ${maxConnections.max}")
}

fun main() {
// No confusion about parameter meaning
updateUserSettings(
Timeout(30),
RetryCount(3),
ConnectionLimit(5)
)
}

Limitations and Edge Cases​

Value classes have some limitations you should be aware of:

1. Boxing in Some Cases​

Value classes don't always eliminate the wrapper object. Boxing (creating an actual instance) occurs in these situations:

kotlin
value class Name(val value: String) {
fun greet() {
println("Hello, $value!")
}
}

fun main() {
val name = Name("Alice")

// These use cases will cause boxing (creating actual objects):

// 1. Using as a nullable type
val nullableName: Name? = name

// 2. Using as a generic type parameter
val namesList = listOf(name)

// 3. Using the value class as a receiver for extension function
name.greet()
}

2. Property and Function Restrictions​

Value classes can only have one primary property, but they can have additional properties if they're derived from the primary one:

kotlin
value class Email(val address: String) {
// Additional computed properties are OK
val domain: String
get() = address.substringAfter('@')

// Member functions are OK too
fun isValid(): Boolean {
return address.contains('@')
}

// But you cannot have secondary properties with backing fields
// (This would not compile):
// var usageCount: Int = 0
}

fun main() {
val email = Email("user@example.com")
println("Domain: ${email.domain}") // Domain: example.com
println("Is valid: ${email.isValid()}") // Is valid: true
}

Real-World Applications​

Example 1: Money Handling​

Using value classes for representing money ensures type safety and prevents errors:

kotlin
value class Dollars(val amount: BigDecimal) {
operator fun plus(other: Dollars): Dollars = Dollars(amount + other.amount)
operator fun minus(other: Dollars): Dollars = Dollars(amount - other.amount)
}

value class Euros(val amount: BigDecimal) {
operator fun plus(other: Euros): Euros = Euros(amount + other.amount)
operator fun minus(other: Euros): Euros = Euros(amount - other.amount)
}

fun processDollarPayment(payment: Dollars) {
println("Processing $${payment.amount} payment")
}

fun main() {
val salary = Dollars(BigDecimal("5000.00"))
val bonus = Dollars(BigDecimal("1000.00"))
val total = salary + bonus

val euroPayment = Euros(BigDecimal("500.00"))

processDollarPayment(total) // Works fine
// processDollarPayment(euroPayment) // Compile error: prevents mixing currencies
}

Example 2: Secure API Design​

Value classes can help prevent security vulnerabilities by ensuring proper content validation:

kotlin
value class SanitizedHtml private constructor(val value: String) {
companion object {
fun create(input: String): SanitizedHtml {
// Imagine this does proper HTML sanitization
val sanitized = input.replace("<script>", "&lt;script&gt;")
return SanitizedHtml(sanitized)
}
}
}

// API that only accepts sanitized HTML
fun renderUserContent(content: SanitizedHtml) {
println("Rendering HTML safely: ${content.value}")
}

fun main() {
val userInput = "<div>Hello</div><script>alert('XSS attack');</script>"

// This won't compile - enforcing sanitization
// renderUserContent(SanitizedHtml(userInput))

// This works - proper sanitization is ensured
val sanitized = SanitizedHtml.create(userInput)
renderUserContent(sanitized)
}

Migrating from Inline Classes​

If you've used Kotlin's inline classes before, they're now called value classes. The migration is straightforward:

kotlin
// Old syntax (still works but deprecated)
inline class OldSyntax(val value: String)

// New syntax (preferred)
value class NewSyntax(val value: String)

Both declarations work similarly, but value class more accurately reflects the concept and aligns with similar features in other languages.

Best Practices​

  1. Use for domain-specific types: Create value classes for domain concepts like UserId, Email, or Temperature.

  2. Add validation: Consider adding validation in constructors or companion objects.

  3. Keep them immutable: Value classes should typically represent immutable values.

  4. Be aware of boxing: Remember that value classes can still be boxed in some cases.

  5. Document interfaces carefully: When a value class implements an interface, document that this may lead to boxing.

Summary​

Kotlin Value Classes provide an elegant solution to a common problem in strongly-typed languages: how to create domain-specific types without incurring runtime penalties. They're perfect for creating type-safe wrappers around primitive types, ensuring both safety and performance.

By using value classes, you can:

  • Improve type safety with zero overhead
  • Make your API intentions clearer
  • Prevent common errors like mixing incompatible values
  • Create domain-specific types that are both expressive and efficient

Additional Resources​

Exercises​

  1. Create a value class Temperature that wraps a Double value and includes methods to convert between Celsius and Fahrenheit.

  2. Design a small library for handling HTTP requests using value classes for URL, Headers, and RequestBody to ensure type safety.

  3. Implement a money handling system with different currencies as value classes and a safe conversion mechanism between them.

  4. Create a value class for email addresses with validation and domain extraction functionality.



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)