Many classes exist just to hold data: a user with a name and email, a product with a price, a point with x and y, a response from a web API. For these, you usually want the same handful of features: a readable printout, a way to compare two objects by their values, and an easy way to make a slightly changed copy.
Kotlin's data classes give you all of that by adding one word: data. From the properties in the primary constructor, the compiler generates toString(), equals(), hashCode(), copy() and functions for destructuring. A class that would need dozens of lines of boilerplate in many languages becomes a single line.
In this lesson you'll declare data classes, see what the compiler generates, make changed copies instead of modifying objects, unpack objects into variables, and learn when a regular class is the better choice.
Declaring a data class
data class User(val name: String, val email: String, val age: Int)
val u = User("Ada", "ada@example.com", 36)
println(u) // User(name=Ada, email=ada@example.com, age=36)The compiler generates, from the constructor properties:
toString(), giving the readable format above instead of something likeUser@6d06d69c.equals()andhashCode(), so two users with the same values are equal.copy(), to create a modified copy.component1(),component2()and so on, which power destructuring.
The primary constructor must have at least one val or var parameter.
Equality and copy
With a regular class, == on two separately created objects is false, even if their values match, because they're different objects. With a data class, == compares the values:
User("Ada", "a@x.com", 36) == User("Ada", "a@x.com", 36) // trueData classes are usually written with val properties, so objects never change. To "change" one, make a copy with only the differences:
val older = u.copy(age = 37)u is untouched, so any code holding it still sees the original values. This style, called immutable data, avoids a whole family of bugs where one part of a program changes an object another part is still using.
Destructuring
A data class can be unpacked into separate variables in the order of its properties:
val (name, email) = u // takes the first two
for ((name, _, age) in users) { } // _ skips a propertyThis is the same mechanism that unpacks Pair and map entries. Destructuring is by position, not by name, so val (email, name) = u would put the name into email. Keep destructuring to small, obvious cases.
Data classes in collections
Because equality and hash codes are based on values, data classes work naturally as map keys and set items: a setOf containing two equal users holds just one. Sorting, grouping and searching by properties work well with the collection operations you've already learned.
Only properties in the primary constructor take part in the generated functions. A property declared in the class body is ignored by toString, equals and copy, which can be useful for cached or derived values, but surprising if you forget.
Kotlin Data Classes Syntax
Data class essentials
data class Point(val x: Int, val y: Int)
val p = Point(1, 2)
println(p) // Point(x=1, y=2)
p == Point(1, 2) // true
val q = p.copy(y = 5)
val (x, y) = p
setOf(p, Point(1, 2)).size // 1Kotlin Data Classes Summary
data classgenerates toString, equals, hashCode, copy and componentN from constructor properties.==compares data class objects by value.- copy(prop = value) makes a changed copy; prefer val properties for immutable data.
- Destructuring
val (a, b) = objunpacks properties by position. - Only primary constructor properties are used by the generated functions.
Kotlin Data Classes Examples
Each example shows the code, the output it produces and what each part does.
Generated functions
data class Product(val name: String, val price: Double, val stock: Int = 0)
class PlainProduct(val name: String, val price: Double)
fun main() {
val a = Product("Mug", 8.5, 12)
val b = Product("Mug", 8.5, 12)
println(a)
println(a == b)
val c = PlainProduct("Mug", 8.5)
val d = PlainProduct("Mug", 8.5)
println(c == d)
}Product(name=Mug, price=8.5, stock=12)
true
falseLine-by-line explanation
- Line 1One line gives a full-featured data holder.
- Line 7The generated toString lists every property.
- Line 8Data classes compare values, so these are equal.
- Line 12Regular classes compare identity: two separate objects aren't equal.
copy and destructuring
data class Order(val id: Int, val item: String, val qty: Int, val shipped: Boolean = false)
fun main() {
val order = Order(1042, "Notebook", 3)
val updated = order.copy(qty = 5)
val sent = updated.copy(shipped = true)
println(order)
println(sent)
val (id, item, qty) = sent
println("#$id: $qty x $item")
}Order(id=1042, item=Notebook, qty=3, shipped=false)
Order(id=1042, item=Notebook, qty=5, shipped=true)
#1042: 5 x NotebookLine-by-line explanation
- Lines 5 to 6Each copy changes only the named properties; earlier versions stay as they were.
- Line 8The original order still has qty 3.
- Line 11Destructuring takes the first three properties in order.
Data classes in sets, maps and sorting
data class Point(val x: Int, val y: Int)
data class Player(val name: String, val score: Int)
fun main() {
val visited = setOf(Point(0, 0), Point(1, 2), Point(0, 0))
println(visited.size)
val labels = mapOf(Point(1, 2) to "Treasure")
println(labels[Point(1, 2)])
val players = listOf(Player("Ben", 340), Player("Ana", 120), Player("Cleo", 340))
val ranking = players.sortedWith(compareByDescending<Player> { it.score }.thenBy { it.name })
for ((name, score) in ranking) println("$name $score")
}2
Treasure
Ben 340
Cleo 340
Ana 120Line-by-line explanation
- Lines 5 to 6Two equal points count as one in a set.
- Line 9A brand new Point(1, 2) finds the entry, because equal values give equal hash codes.
- Line 12Sort by score, highest first, then by name to break ties.
- Line 13Destructuring works in for loops too.
Common Kotlin Data Classes Mistakes
Using var and changing shared data
data class Cart(var items: MutableList<String>)
val saved = cart
cart.items.add("Lamp") // saved changes too: it's the same objectdata class Cart(val items: List<String>)
val newCart = cart.copy(items = cart.items + "Lamp")Properties in the body are ignored
data class User(val name: String) {
var age: Int = 0 // not in toString, equals or copy
}data class User(val name: String, val age: Int = 0)Destructuring in the wrong order
val (email, name) = User("Ada", "ada@example.com", 36) // email = "Ada"val (name, email) = User("Ada", "ada@example.com", 36)Kotlin Data Classes in Real Projects
Data classes in practice
Data classes model almost every piece of data in modern Kotlin apps: JSON from web APIs (parsed with libraries like kotlinx.serialization and Moshi), database rows with Room, and screen state in Jetpack Compose, where a new copy of the state triggers the screen to redraw. Their value-based equality makes them easy to test, too: assertEquals(expectedUser, actualUser) just works.
Kotlin Data Classes FAQ
Can a Kotlin data class have methods?
Yes. A data class body can contain functions and computed properties like any other class, such as fun pounds() = pence / 100.0 on a Money class. Keep them about the data itself; behaviour that changes state over time usually belongs in a regular class.
Can a data class inherit from another class?
A data class can extend an open or abstract class and implement interfaces, which is common with sealed hierarchies. But it can't be open itself, so nothing can inherit from a data class. Writing open data class is a compile error.
Does copy() make a deep copy of a data class?
No, copy() is shallow. The new object gets the same property values, so if a property holds a MutableList, both objects share that one list and a change through either is visible in both. Using read-only List properties, and building new lists when things change, avoids the problem.
Can I override toString in a data class?
Yes. If you write your own override fun toString(), the compiler uses yours instead of generating one, and the same goes for equals and hashCode. A common reason is hiding sensitive fields, such as a password, so they never end up in logs.
Should I use a Pair or a data class?
A Pair is fine for a quick, local result, such as returning a minimum and maximum together. Once the values travel further through your code, a data class is clearer, because result.low says far more than result.first. Data classes also let you add more properties later without changing every caller.
Kotlin Data Classes Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the declaration so Point gets a generated toString and equals.
class Point(val x: Int, val y: Int)
What does this print?
data class Pet(val name: String, val age: Int)
println(Pet("Rex", 3))
What does this print?
data class Size(val w: Int, val h: Int)
val a = Size(2, 3)
val b = a.copy(h = 9)
println("${a.h} ${b.h}")
Which properties does a data class use for equals and toString?
Which is the best fit for a regular class rather than a data class?
Kotlin Data Classes Quiz
5 questions to check what you learned.
What does
copy()do on a data class?- Creates a new object, optionally with some properties changed
- Copies it to the clipboard
- Changes the original object
- Deletes the object
How does
==compare two data class objects?- By memory address
- By their names
- It always returns false
- By the values of their constructor properties
How does destructuring decide which value goes where?
- By property name
- By property position in the constructor
- Alphabetically
- Randomly
How many items does
setOf(Point(1, 1), Point(1, 1))contain, with Point a data class?- 2
- 0
- An error
- 1
What's the minimum requirement for a data class constructor?
- At least one val or var parameter
- No parameters
- Exactly two parameters
- All parameters must be var