---
title: "Value Classes em Kotlin: Performance e Type Safety | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/kotlin-value-classes/"
markdown_url: "https://kotlin.dev.br/blog/kotlin-value-classes.MD"
description: "Aprenda value classes em Kotlin: @JvmInline, typed IDs, unidades de medida e modelagem de domínio. Guia prático com exemplos reais."
date: "2026-03-27"
author: "Karina Melo"
---

# Value Classes em Kotlin: Performance e Type Safety | Kotlin Brasil

Aprenda value classes em Kotlin: @JvmInline, typed IDs, unidades de medida e modelagem de domínio. Guia prático com exemplos reais.


Quantas vezes você já passou um `String` onde deveria ser um ID, ou confundiu metros com quilômetros porque ambos eram `Double`? Esses bugs silenciosos são comuns em projetos Kotlin — e as **value classes** existem justamente para eliminá-los. Neste guia, vamos explorar tudo sobre value classes: desde a sintaxe básica até padrões avançados de modelagem de domínio, sempre com foco em performance e type safety.

## O que São Value Classes?

Value classes (anteriormente chamadas de inline classes) são um recurso do Kotlin que permite criar tipos wrapper **sem custo de alocação em runtime**. O compilador substitui a value class pelo valor interno sempre que possível, eliminando a criação de objetos no heap.

```kotlin
@JvmInline
value class UserId(val value: Long)

@JvmInline
value class Email(val value: String)

fun findUser(id: UserId): User? { /* ... */ }

// Em tempo de compilação: tipagem forte
// Em runtime: é apenas um Long (sem boxing)
val id = UserId(42)
findUser(id) // ✅ Compila
findUser(UserId(99)) // ✅ Compila
// findUser(42) // ❌ Não compila — type safety!
```

A anotação `@JvmInline` é obrigatória desde o Kotlin 1.5 e indica ao compilador que esta classe deve ser "inlineada" — ou seja, o wrapper é removido no bytecode gerado.

## Evolução: De Inline Classes para Value Classes

As value classes são a evolução das inline classes experimentais do Kotlin 1.2. A mudança de nome reflete a intenção do time do Kotlin de, no futuro, suportar value classes com múltiplas propriedades (Valhalla Project da JVM). Por enquanto, a restrição é:

- Exatamente **uma propriedade** no construtor primário
- A propriedade deve ser `val` (imutável)
- Anotação `@JvmInline` obrigatória para a JVM

```kotlin
// ✅ Válido
@JvmInline
value class ProductName(val name: String)

// ❌ Inválido — duas propriedades
// @JvmInline
// value class Money(val amount: Double, val currency: String)
```

## Casos de Uso Práticos

### 1. Typed IDs (IDs Tipados)

O caso de uso mais clássico. Em vez de passar `Long` ou `String` para tudo, cada entidade tem seu próprio tipo de ID:

```kotlin
@JvmInline
value class UserId(val value: Long)

@JvmInline
value class OrderId(val value: Long)

@JvmInline
value class ProductId(val value: Long)

// Impossível confundir um ID com outro
fun getOrder(orderId: OrderId): Order? { /* ... */ }
fun getUser(userId: UserId): User? { /* ... */ }

val orderId = OrderId(100)
val userId = UserId(200)

getOrder(orderId) // ✅
// getOrder(userId) // ❌ Type mismatch — erro em tempo de compilação!
```

Esse padrão é especialmente poderoso em projetos com [arquitetura limpa](/guias/guia-clean-architecture-kotlin/) onde a camada de domínio precisa de tipos expressivos.

### 2. Unidades de Medida

Evite confusões entre unidades usando value classes:

```kotlin
@JvmInline
value class Meters(val value: Double) {
    fun toKilometers(): Kilometers = Kilometers(value / 1000.0)
    operator fun plus(other: Meters): Meters = Meters(value + other.value)
    operator fun compareTo(other: Meters): Int = value.compareTo(other.value)
}

@JvmInline
value class Kilometers(val value: Double) {
    fun toMeters(): Meters = Meters(value * 1000.0)
}

@JvmInline
value class Celsius(val value: Double) {
    fun toFahrenheit(): Fahrenheit = Fahrenheit(value * 9.0 / 5.0 + 32.0)
}

@JvmInline
value class Fahrenheit(val value: Double)

// Uso
val distance = Meters(1500.0)
val km = distance.toKilometers()
println("${distance.value}m = ${km.value}km") // 1500.0m = 1.5km
```

### 3. Validação no Construtor

Combine value classes com blocos `init` para garantir invariantes:

```kotlin
@JvmInline
value class Email(val value: String) {
    init {
        require(value.contains("@") && value.contains(".")) {
            "Email inválido: $value"
        }
    }
}

@JvmInline
value class Percentage(val value: Double) {
    init {
        require(value in 0.0..100.0) {
            "Porcentagem deve estar entre 0 e 100: $value"
        }
    }
}

@JvmInline
value class NonBlankString(val value: String) {
    init {
        require(value.isNotBlank()) { "String não pode ser vazia" }
    }
}

// Falha rápido — melhor que descobrir o bug em produção
val email = Email("dev@kotlin.dev.br") // ✅
// val invalid = Email("sem-arroba") // ❌ IllegalArgumentException
```

### 4. Wrapper para APIs Externas

Torne APIs de terceiros mais seguras com tipagem:

```kotlin
@JvmInline
value class JsonString(val raw: String)

@JvmInline
value class BearerToken(val token: String) {
    fun toHeader(): String = "Bearer $token"
}

@JvmInline
value class Url(val value: String) {
    init {
        require(value.startsWith("http://") || value.startsWith("https://")) {
            "URL inválida: $value"
        }
    }
}

fun callApi(url: Url, token: BearerToken): JsonString {
    // Impossível passar token onde deveria ser URL e vice-versa
    // ...
    return JsonString("""{"status": "ok"}""")
}
```

## Performance: Value Classes vs Data Classes

A principal vantagem de value classes sobre [data classes](/tutoriais/data-classes-tutorial/) é a **ausência de alocação no heap** na maioria dos cenários:

```kotlin
// Data class — SEMPRE aloca um objeto no heap
data class UserIdData(val value: Long)

// Value class — NÃO aloca objeto (inlined como Long)
@JvmInline
value class UserIdValue(val value: Long)

fun processDataId(id: UserIdData) = id.value * 2
fun processValueId(id: UserIdValue) = id.value * 2

// No bytecode:
// processDataId recebe um objeto UserIdData
// processValueId recebe um long primitivo
```

### Quando Ocorre Boxing?

Value classes **são** alocadas no heap (boxing) em cenários específicos:

```kotlin
@JvmInline
value class Name(val value: String)

// ❌ Boxing ocorre aqui:
val nullable: Name? = Name("Kotlin")       // Nullable → boxing
val list: List<Name> = listOf(Name("A"))   // Generic → boxing
val any: Any = Name("B")                   // Upcasting → boxing

// ✅ Sem boxing aqui:
val name: Name = Name("Kotlin")            // Uso direto → inlined
fun greet(name: Name) = "Olá, ${name.value}" // Parâmetro → inlined
```

Na prática, para a maioria dos casos de uso (parâmetros de função, variáveis locais, retornos), o compilador elimina a alocação. O boxing acontece apenas quando o tipo precisa ser "apagado" por nullability ou generics.

## Value Classes e Interfaces

Value classes podem implementar interfaces, o que abre possibilidades interessantes:

```kotlin
interface Identifiable {
    val id: String
}

@JvmInline
value class SkuCode(val code: String) : Identifiable {
    override val id: String get() = code
}

@JvmInline
value class Barcode(val code: String) : Identifiable {
    override val id: String get() = code
}

fun logIdentifiable(item: Identifiable) {
    println("Processing: ${item.id}")
}
```

**Atenção:** quando uma value class é usada como interface (upcast), ocorre boxing. Use interfaces com value classes conscientemente.

## Limitações

Value classes têm algumas restrições importantes:

1. **Apenas uma propriedade** no construtor primário (por enquanto)
2. **Não podem ter `lateinit` ou propriedades delegadas**
3. **Não podem participar de hierarquias de classe** — não podem ser `open`, `abstract` ou `sealed`
4. **Boxing em contextos genéricos e nullable**
5. **Não podem ter backing fields** além da propriedade primária

Se você precisa de mais de uma propriedade, use uma [data class](/tutoriais/data-classes-tutorial/) ou aguarde futuras versões do Kotlin com suporte a multi-property value classes.

Para representar estados complexos com restrições de tipo, considere combinar value classes com [sealed classes](/blog/sealed-classes-kotlin/):

```kotlin
@JvmInline
value class OrderId(val value: Long)

sealed interface OrderStatus {
    data class Pending(val orderId: OrderId) : OrderStatus
    data class Shipped(val orderId: OrderId, val trackingCode: String) : OrderStatus
    data class Delivered(val orderId: OrderId) : OrderStatus
}
```

## Comparação Rápida

| Aspecto | Value Class | Data Class | Type Alias |
|---|---|---|---|
| Type safety | ✅ Tipo distinto | ✅ Tipo distinto | ❌ Mesmo tipo |
| Alocação heap | ❌ Geralmente não | ✅ Sempre | N/A |
| Múltiplas props | ❌ Apenas uma | ✅ Sim | N/A |
| equals/hashCode | ✅ Automático | ✅ Automático | N/A |
| copy() | ❌ Não | ✅ Sim | N/A |

## Boas Práticas

1. **Use value classes para IDs e identificadores** — é o caso de uso com melhor relação custo-benefício
2. **Adicione validação no `init`** — falhe rápido em vez de propagar dados inválidos
3. **Prefira value classes a type aliases** quando precisar de type safety real
4. **Evite em contextos genéricos** se performance for crítica (listas, maps, nullable)
5. **Combine com [sealed classes](/blog/sealed-classes-kotlin/)** para modelagem de domínio expressiva
6. **Documente o comportamento de boxing** para o time entender quando a alocação acontece

## Conclusão

Value classes são uma das features mais subestimadas do Kotlin. Elas oferecem **type safety sem custo de runtime** na maioria dos cenários — algo que poucas linguagens conseguem entregar. Se você quer escrever código Kotlin mais seguro, mais expressivo e mais performático, comece adotando value classes nos seus IDs e tipos primitivos de domínio. Para mais sobre modelagem e boas práticas, confira nosso guia de [design patterns em Kotlin](/blog/kotlin-design-patterns/) e o artigo sobre [scope functions](/blog/scope-functions-kotlin/).

Se você vem de outras linguagens, vale notar que <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go usa type definitions com custo zero semelhante</a>, enquanto <a href="https://rustlang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust tem o padrão newtype que inspira as value classes do Kotlin</a>. Já em <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python, o módulo typing oferece NewType</a> para segurança estática, embora sem os ganhos de performance em runtime que o Kotlin proporciona.
