Data class in Kotlin: why they cannot be inherited and how to work around it

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Data class in Kotlin: why they cannot be inherited and how to work around it (under the hood analysis)

\n\n

Have you ever written data class User(val name: String), and then wanted to do data class Admin extends User? And got a red error from the compiler? If yes — you are not alone. The question «Extend data class in Kotlin» is among the top most popular on Stack Overflow (over 50,000 views).

\n\n

Today we will not just give a ready answer. We will look under the hood of Kotlin, analyze why the language creators went for such a "harsh" limitation, and show 3 legal ways to bypass it without breaking the architecture.

\n\n

The article is written for the educational portal PythonLib — here we love to dig deep, not just copy snippets.

\n\n

Why can't a data class be inherited? Let's look at the bytecode

\n\n

Let's get straight to the point. Let's write a simple data class and see what the compiler generates:

\n\n
data class User(val name: String, val age: Int)
\n\n

Under the hood, Kotlin creates:

\n
    \n
  • equals() — compares all fields
  • \n
  • hashCode() — hash based on all fields
  • \n
  • toString() — nice string representation
  • \n
  • copy() — copy function with field modification
  • \n
  • componentN() — destructuring
  • \n
\n\n

Now imagine you inherit a data class:

\n\n
open data class Base(val x: Int)\ndata class Derived(val x: Int, val y: Int) : Base(x) // Compilation error!
\n\n

Kotlin outputs: «Inheritance of data classes is not allowed». Why? Let's figure it out.

\n\n

Problem #1: copy() breaks. The copy() function is generated only for the fields of the current class. If you have Derived inherited from Base, then copy() in Base does not know about the y field. Calling base.copy() will lose the inheritor's data.

\n\n

Problem #2: equals() and hashCode(). Imagine: Base(1) and Derived(1, 2). Should they be equal? If yes — symmetry is violated (since Derived contains more fields). If no — the equals/hashCode contract is violated. Kotlin chose the safe path — to prohibit inheritance.

\n\n

Problem #3: componentN(). val (x) = base and val (x, y) = derived — different numbers of components. Inheritance would cause confusion.

\n\n

Bottom line: data class is created for immutable data containers, not for hierarchies. This is a conscious language design decision.

\n\n

Method #1: Composition instead of inheritance (recommended)

\n\n

The cleanest way is not to inherit, but to include one data class inside another. This is called composition.

\n\n
data class Address(val city: String, val street: String)\n\ndata class User(\n    val name: String,\n    val age: Int,\n    val address: Address // composition\n)\n\nfun main() {\n    val address = Address("Moscow", "Tverskaya")\n    val user = User("Anna", 25, address)\n    println(user) // User(name=Anna, age=25, address=Address(city=Moscow, street=Tverskaya))\n    \n    // Copying works perfectly\n    val user2 = user.copy(age = 26)\n    println(user2) // User(name=Anna, age=26, address=Address(city=Moscow, street=Tverskaya))\n}\n
\n\n

Pros: everything works "out of the box", no surprises with equals/hashCode, code is transparent.

\n\n

Cons: if you need many levels of nesting, you get a "matryoshka". But this is solved by data normalization.

\n\n

Method #2: Sealed class + data class (for strict hierarchies)

\n\n

If you really need a hierarchy (e.g., different user types), use sealed class (or sea

Blogs

Book Recommendations