Question
Can You Extend a Data Class in Kotlin? Inheritance, Limits, and Better Alternatives
Question
In Kotlin, I want to use inheritance with a data class, similar to how I might extend a plain Java POJO.
For example, I would like to write code like this:
open data class Resource(var id: Long = 0, var location: String = "")
data class Book(var isbn: String) : Resource()
However, this fails because of a clash between generated component1() methods. If I remove the data modifier from one of the classes, that still does not fully solve the design problem.
I am looking for the Kotlin idiom here: is there a proper way to extend a data class?
One possible workaround is to make the parent class open and override all inherited properties in the child data class:
open class Resource(open var id: Long = 0, open var location: String = "")
data class Book(
override var id: Long = 0,
override var location: String = "",
var isbn: String
) : Resource()
But this feels repetitive and awkward. Is there a better approach?
Short Answer
By the end of this page, you will understand why Kotlin data class types do not work well with inheritance, why generated methods like componentN() cause conflicts, and which alternatives are typically used instead. You will also see practical patterns such as composition, interfaces, and using a regular base class with a data-class child only when it truly fits.
Concept
Kotlin data class is designed for holding data, not for participating in deep inheritance hierarchies.
When you mark a class as data, Kotlin automatically generates useful methods based on the properties declared in the primary constructor, including:
equals()hashCode()toString()copy()componentN()functions for destructuring
These generated methods are tightly tied to the exact constructor properties of that class. Because of that, inheritance becomes problematic.
Why extending a data class is not supported well
A data class in Kotlin cannot be open, so this is invalid:
open data class Resource(var id: Long = 0, var location: String = "")
Kotlin intentionally prevents this because inheritance would make the generated methods ambiguous or misleading.
For example:
Mental Model
Think of a Kotlin data class like a prebuilt identity card generator.
When you create a data class, Kotlin automatically prints a card with:
- how to compare the object
- how to copy it
- how to list its fields
- how to destructure it
That generator expects a single, clear list of fields from one class.
Inheritance is like trying to combine two identity card generators from a parent and a child. Now the system no longer knows:
- which fields define identity
- which order the fields should appear in
- which generated methods should win
So Kotlin avoids this confusion by keeping data classes simple and final.
A good rule of thumb is:
- Use data classes for plain value objects
- Use inheritance for shared behavior only when you really need polymorphism
- Use composition when one object naturally contains another
Syntax and Examples
Core rule
A Kotlin data class:
- cannot be
open - cannot extend another
data class - may extend a regular class or implement an interface, but this must be designed carefully
Invalid example
open data class Resource(var id: Long = 0, var location: String = "")
This is invalid because data class cannot be open.
Better option 1: composition
data class Resource(
val id: Long = 0,
val location: String = ""
)
data class Book(
val resource: Resource,
val isbn: String
)
Step by Step Execution
Consider this composition-based example:
data class Resource(val id: Long, val location: String)
data class Book(val resource: Resource, val isbn: String)
val book = Book(Resource(10, "Room 2"), "978-1111111111")
println(book)
Step by step
1. Resource is defined
data class Resource(val id: Long, val location: String)
Kotlin generates:
equals()hashCode()toString()copy()component1()forid
Real World Use Cases
API response models
When working with API data, you often receive nested objects instead of inheritance-based models.
data class UserMeta(val id: Long, val location: String)
data class User(val meta: UserMeta, val name: String)
This mirrors JSON structures cleanly.
Database or persistence models
A base record such as id and timestamps is often shared across entities. Instead of inheriting data classes, teams frequently:
- use a regular base class for framework needs, or
- keep common fields duplicated in separate data classes, or
- wrap shared fields in a nested value object
Domain modeling
If Book has a resource identity, composition reads naturally:
- a book has resource info
- an image has resource info
- a video has resource info
That is often clearer than forcing an inheritance tree.
UI state objects
In Android or desktop apps, data classes are commonly used for immutable view state.
( message: String)
( items: List<String>)
Real Codebase Usage
In real Kotlin projects, developers usually avoid data-class inheritance and prefer a few practical patterns.
1. Composition for shared fields
A common pattern is to group reusable fields into another class.
data class AuditInfo(val createdBy: String, val updatedBy: String)
data class Article(
val title: String,
val auditInfo: AuditInfo
)
This keeps models modular and readable.
2. Interfaces for shared contracts
If multiple types must expose the same properties, an interface is often enough.
interface Identifiable {
val id: Long
}
Then any data class can implement it without inheriting generated data-class behavior.
3. Regular base classes for framework integration
Sometimes frameworks require a superclass. In that case, teams use:
- a normal
open classfor shared behavior - data classes only at the leaf level, if useful
But they do this carefully because equality and copying semantics can become less obvious.
4. Prefer val for value models
Common Mistakes
Mistake 1: Trying to make a data class open
open data class Resource(val id: Long)
This is not allowed.
Avoid it by
- using a regular
open class, or - using composition instead
Mistake 2: Expecting inherited properties to be part of child data-class generation automatically
open class Resource(open val id: Long)
data class Book(val isbn: String) : Resource(1)
Here, Book's generated methods only consider properties in Book's primary constructor.
So id is not part of Book's equals(), copy(), or destructuring unless you redeclare it in the child constructor.
Comparisons
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Data class + composition | Reusing shared data | Clear, safe, idiomatic, full data-class support | Requires nested access like book.resource.id |
| Data class + interface | Shared contract | Flexible, no state collision, good for polymorphism | Does not reuse implementation or storage |
| Data class child of regular class | Frameworks or limited inheritance needs | Possible in some cases | Repetitive, inherited fields may need redeclaration |
| Data class extending data class | Not supported | None | Conflicts with generated methods and semantics |
Inheritance vs composition
| Style | Meaning |
|---|
Cheat Sheet
Quick rules
data classcannot beopen- one data class should not inherit from another data class
- generated methods are based only on properties in the primary constructor
- inherited properties are not automatically included in child data-class features unless redeclared in the child constructor
Common alternatives
Composition
data class Resource(val id: Long, val location: String)
data class Book(val resource: Resource, val isbn: String)
Interface
interface HasId { val id: Long }
Regular base class
open class Resource(open val id: Long)
data ( id: , isbn: String) : Resource(id)
FAQ
Can a data class inherit from another data class in Kotlin?
No. Kotlin does not support data-class inheritance because generated methods like copy() and componentN() would create ambiguous behavior.
Why can't a data class be open in Kotlin?
Because data classes generate methods based on a fixed set of constructor properties. Allowing subclasses would make equality, copying, and destructuring semantics unclear.
Can a data class extend a regular class?
Yes, it can extend a non-data class if the design allows it. But only the properties declared in the data class's own primary constructor are used for generated methods.
Are inherited properties included in a child data class's equals and copy?
Not automatically. Only properties in the child data class primary constructor are included.
What is the best alternative to data-class inheritance in Kotlin?
Usually composition. If one object contains another object's data, composition is cleaner and avoids generated-method conflicts.
When should I use an interface instead of inheritance?
Use an interface when multiple classes should expose the same properties or behavior contract, but you do not need shared stored state.
Is repeating parent properties in a child data class a good idea?
It works, but it is often verbose and a sign that the model may be better represented with composition.
Mini Project
Description
Create a small library catalog model where books need shared resource information such as id and location, but without using data-class inheritance. This demonstrates the Kotlin-idiomatic approach of composition.
Goal
Build a Book model that reuses resource data cleanly and supports printing, copying, and destructuring without inheritance issues.
Requirements
- Create a
Resourcedata class withidandlocation - Create a
Bookdata class that contains aResourceand anisbn - Create at least two
Bookobjects and print them - Use
copy()to change one field in a book - Destructure one
Bookobject and print its parts
Keep learning
Related questions
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.
Difference Between List and Array in Kotlin
Learn the difference between List and Array in Kotlin, including mutability, size, APIs, performance, and when to use each one.
Fix KaptExecution Error in Android Kotlin: Data Binding and Build Generation
Learn why KaptExecution fails in Android Kotlin builds, especially with data binding and generated classes, and how to fix it step by step.