Open almost any Kotlin project and you’ll find a folder of classes that do nothing but hold a few values. A user, a response from some endpoint, the state of a screen. The data keyword exists for those. Put it in front of a class and the compiler writes equals(), hashCode(), toString(), copy(), and componentN() for you, all based on the properties in the primary constructor.
JetBrains builds the language, and you’ll run into data classes constantly in Android models, API response objects, and small value containers passed between functions. Kotlin holds a 51 percent admiration rate among developers who used it over the past year, according to Stack Overflow’s 2025 Developer Survey. My guess is data classes are part of why.
What Is a Kotlin Data Class?

Short version: it’s a class meant to hold values, not behavior. You mark it with data, and that one keyword is the only change to the declaration.
Kotlin is JetBrains’s statically typed language for the JVM and Android, and the company treats this as one of its signature productivity features. You write the properties once. The compiler handles equals, hashCode, toString, copy, and the componentN functions.
In practice they end up as DTOs parsed from API responses (Retrofit or Ktor, usually) and as database rows mapped through Room. UI state objects that a ViewModel hands to a screen are another big one. Pretty much anywhere a codebase needs a small, predictable container.
Kotlin now runs inside more than 95 percent of the top 1,000 Android apps, and over half of professional Android developers list it as their primary language for Android development (Google Developers Blog, 2023). So if you do Android work, you’re going to read a lot of these.
One line of data class code typically replaces what used to be five hand-written methods.
What Functions Does a Data Class Generate Automatically?

Everything comes from the primary constructor. You don’t write any of it.
equals() and hashCode() compare instances by property value instead of memory reference. toString() prints a readable line like User(name=John, age=42), which beats User@6d06d69c in a log file by a wide margin. copy() builds a new instance with whichever properties you want changed. And every property gets its own componentN() function (component1(), component2(), and so on), which is what makes destructuring declarations work.
One catch. Only properties declared in the primary constructor count. Anything you add inside the class body gets skipped by all of these.
A 2026 JetBrains Research study tracked roughly 28 million edit-to-push cycles across real projects and found Kotlin developers finished comparable work 15 to 20 percent faster than developers writing Java (JetBrains, 2026). The study named data classes directly. Swapping five hand-written methods for one declaration removes a whole category of repetitive edits, so that tracks.
If you write your own equals(), hashCode(), or toString() inside the class body, generation turns off for that one function only. The rest are still generated.
How Does a Data Class Determine Equality?
By values, not identity.
The compiler builds equals() and hashCode() from every property in the primary constructor. Two instances holding the same values count as equal even when they sit at different memory addresses. This is called structural equality, and every class marked data gets it by default.
Say you create User(“Ana”, 29) in two different parts of a program. With a data class, equals() returns true and the hashCode() values match. Remove the data modifier and the same comparison returns false, unless someone wrote equals() by hand. A regular class falls back to referential equality, where two instances are equal only if they point to the same object in memory.
How Does the Copy Function Create New Instances?
You call copy() with named arguments for the properties you want to change. Everything you leave out keeps its current value, and you get a new instance back.
I like that the named arguments make the override obvious at the call site. The part people miss is that copy() does a shallow copy, not a deep copy. If a property holds a mutable list or some other mutable object, the original and the new instance share it. Change it through one and you’ve changed it in both.
As of the Kotlin 2.0.20 update in August 2024, the compiler started warning when a copy() function exposes a constructor that was declared private, since copy() previously stayed public no matter what visibility the constructor had (kotlinlang.org, 2024). If your team gates object creation behind a companion object factory function, pay attention to this one. copy() could otherwise skip that gate entirely.
How Does Destructuring Work With Data Class Properties?
val (name, age) = user unpacks both properties into separate variables in one line. Behind that, component1() maps to the first constructor property and component2() to the second, and it keeps going from there. You’ll see it most when looping over a list of data class instances.
Kotlin doesn’t match by property name here. It goes by declaration order in the primary constructor, because that’s what the componentN() functions are generated from. The variable names you pick at the call site mean nothing to the compiler.
So reordering the constructor parameters quietly changes what every existing destructuring call unpacks. Nothing fails to compile. The values just land in the wrong variables. I’ve seen this slip through review because the diff only touched the class file, and nobody thought to check the call sites.
What Are the Primary Constructor Requirements for a Data Class?

The rules are stricter than on a regular class. The primary constructor needs at least one parameter (an empty one isn’t allowed), and every parameter has to be marked val or var.
Leave val or var off even one parameter and the class won’t compile. There’s no silent fallback where Kotlin treats it as a plain constructor argument.
The generated members always trace back to the primary constructor, never a secondary one. Properties added inside the class body stay invisible to equals, hashCode, toString, copy, and componentN.
Beyond that, it’s the same as how Kotlin constructors work in general. Default parameter values and named arguments behave exactly the way they do in an ordinary constructor.
Can a Data Class Extend a Class or Implement an Interface?

Interfaces are no problem at all, implement as many as you want. Extending another class works too since Kotlin 1.1, though the data class itself is always final.
| Capability | Regular class | Data class |
|---|---|---|
| Implement interfaces | Yes | Yes |
| Extend another class | Yes | Yes, since Kotlin 1.1 |
| Be marked open or abstract | Yes | No |
| Be extended by a subclass | Yes, if open | No, always final |
Modifiers That Are Not Allowed
- abstract
- open
- sealed
- inner
Any of these would let a subclass exist, and a subclass would break the equals, hashCode, and copy contracts the compiler generated for the parent. Kotlin just refuses the combination.
Extending Classes and Sealed Hierarchies
JetBrains released Kotlin 1.1 in March 2017, and that’s the version that let data classes extend other classes. Before it, interfaces were the only option.
Where this gets used most is a sealed class as the shared parent, with data classes as the concrete states underneath. Each state carries its own properties. The sealed parent gives a when expression exhaustive coverage, so the compiler complains if you forget a case.
The data class is still final, which means it can extend a class but never be extended, and two data classes can’t inherit from each other. One more thing that trips people up: a property declared on the parent needs val or var repeated on the child if you want it counted in equals and hashCode.
Kotlin Data Class vs Regular Class vs Java Record

Java records go after the same problem. Both cut boilerplate for classes that mostly hold values, but they disagree on mutability, and they came about very differently.
| Type | Generated functions | Mutability | Inheritance support |
|---|---|---|---|
| Kotlin data class | equals, hashCode, toString, copy, componentN | val or var, mutable by default with var | Can extend a class since Kotlin 1.1, cannot itself be extended |
| Regular Kotlin class | None, written by hand | val or var, fully developer controlled | Can extend and be extended if marked open |
| Java record | Constructor, accessors, equals, hashCode, toString | Fields implicitly final, always immutable | Cannot extend a class, can implement interfaces |
Java finalized records under JEP 395 in Java 16, released by Oracle on March 16, 2021, after two rounds of preview in Java 14 and Java 15 (Oracle, 2021). A record’s fields are implicitly final. You don’t get a var option the way you do in Kotlin.
A regular Kotlin class gives up every generated function in exchange for full control over inheritance. That’s only worth it when the class has real behavior beyond holding values.
When Should You Use a Kotlin Data Class?

Use one when the object’s whole job is carrying values from one part of the program to another.
The upside is mostly about code you don’t have to write. Five functions get generated instead of written and maintained by hand, and equality and hashing behave predictably out of the box. copy() also makes it simple to update one field on an otherwise immutable object.
There are real downsides, though. You can’t adjust equals, hashCode, or toString individually without overriding each one by hand. Detekt’s LongParameterList rule exempts data classes from its default 7-parameter constructor threshold, so a data class can grow unwieldy without any tooling warning (Detekt documentation). I’ve opened data classes with 20-plus fields. Not fun. And a data class can’t carry real behavior or enforce invariants the way a class with private state and validation logic can.
Android’s own Jetpack Compose documentation models UI state as data classes passed into composable functions (Android Developers documentation). That’s a pretty good sign of where the pattern earns its keep.
When Does a Kotlin Data Class Not Apply?

Once an object needs to do more than store values, a data class starts working against you.
- Class hierarchies where several open subclasses share and modify state
- Objects with a huge number of properties, since nothing in the compiler stops a constructor from growing past a reasonable size
- Comparison logic that should ignore some properties, or weight them differently
- Objects created by reflection-heavy frameworks that expect a no-argument constructor
The abstract, open, sealed, and inner restrictions from earlier rule out the first one directly. Most of the others have a workaround, but each workaround adds complexity that a plain class would handle more honestly.
How Do You Create a Data Class in Kotlin?

Same as any Kotlin class, plus two extra rules.
- Add the data keyword directly before class
- Declare every primary constructor property with val or var
- Instantiate the class through its constructor like any other class
- Call a generated function such as copy(), toString(), or a destructuring declaration to confirm it works
- Check the IDE for a warning if a property was declared without val or var
Converting an existing regular class into a data class is a common piece of code refactoring. Android Studio usually flags the opportunity with a quick-fix suggestion, and accepting it rewrites the class declaration without touching the rest of the file.
A Kotlin cheat sheet is handy for getting the val, var, and default-value syntax right the first time. Small syntax slips here are the most common reason a property silently drops out of the generated functions.
How Do You Serialize a Kotlin Data Class to JSON?

With Kotlin Serialization, you add the @Serializable annotation to the data class. That triggers a compiler plugin bundled with the Kotlin distribution itself, not a separate reflection library (Kotlin Serialization guide).
Retrofit response models map a RESTful API response straight onto a data class, with one constructor property expected per JSON field the server returns.
Gson and Moshi both need extra care. Google’s own documentation now advises against Gson for JSON on Android, since its open-ended reflection doesn’t survive code shrinking and obfuscation cleanly, and it recommends Kotlin Serialization or Moshi instead (Gson documentation, Google). I’d take that advice. Moshi has a different problem: plain Java-based reflection doesn’t work on Kotlin classes at all, so a data class needs Moshi’s Kotlin code generation or its reflection adapter to parse correctly (Moshi documentation, Square).
FAQ on What Are Kotlin Data Classes
What Is a Kotlin Data Object?
You get the singleton pattern of a Kotlin object, plus the generated toString() and equals() of a data class.
It fits a state that needs exactly one instance, like an empty or loading state in a sealed hierarchy.
Is a Data Class Good for Android Models Like Room or Retrofit?
Yes. Room maps each primary constructor property to a database column through annotations like @Entity and @PrimaryKey.
Retrofit does the same thing for JSON fields. Both rely on the exact constructor shape a data class already enforces.
Should You Use a Data Class or a Sealed Class for State?
Usually both. A sealed class defines the fixed set of possible states, and each state is typically written as a data class inside it.
The sealed class shapes the states. The data classes carry the values.
Is a Kotlin Data Class Immutable by Default?
Not automatically. It’s immutable only if every constructor property uses val, because any var can still be changed directly.
With all vals in place, copy() becomes the standard way to get a changed value without touching the original instance.
What Common Mistakes Happen When Using Data Classes?
Mutable var properties are the big one. They break the equality guarantee once a property changes after two instances were compared (or after one went into a HashSet, which is worse).
Long property lists that make constructors unreadable come up a lot too. So does extending a data class from an abstract parent with shared mutable state.
What Should You Fix First in What Are Kotlin Data Classes?
Start with constructor scope. Properties left outside the primary constructor silently vanish from equals, hashCode, toString, and copy, and at that stage inheritance, mutability, and serialization choices matter far less.
After that, look at where var properties could cause trouble. Picking a serialization library can wait until last.
Ninety-five percent of the top 1,000 Android apps already run Kotlin, yet its admiration score sits at 51 percent among developers who used it last year. That gap suggests the satisfaction is concentrated in production Android teams more than in the wider survey population.
Kotlin singleton classes take the same single-instance idea further into full application state, managed with the object declaration keyword instead of a constructor.
- Google Play Account Suspended: What to Do - October 5, 2026
- How to Plan a Successful Data Migration Without Disrupting Business Operations - October 5, 2026
- How to Turn On Dark Mode in Notepad++ (Built-In, No Plugin) - October 3, 2026



