# Data Object Kotlin vs Regular Object: Key Differences and Usage Guide

> Understand Kotlin data object vs regular object differences. Learn automatic method generation for data structures and improve your code.

- Repository: [JetBrains/kotlin](https://github.com/jetbrains/kotlin)
- Tags: deep-dive
- Published: 2026-02-16

---

**A data object in Kotlin is a singleton that automatically generates `toString()`, `equals()`, and `hashCode()` methods like a data class, while a regular object requires manual implementation of these methods.**

The Kotlin compiler treats data objects as a distinct language feature that bridges the gap between singleton objects and data classes. According to the JetBrains/kotlin repository source code, data objects provide built-in structural equality and string representation without the boilerplate code required by regular objects.

## What Is a Data Object in Kotlin?

A data object is a singleton declaration that combines the single-instance semantics of an `object` with the automatic method generation of a `data class`. When you declare `data object Singleton`, the compiler generates implementations for `toString()`, `equals()`, and `hashCode()` automatically, whereas a regular `object` declaration generates none of these methods by default.

This feature is particularly useful when you need a singleton to represent a specific data value or state within your application, such as a unique configuration instance or a sealed class variant.

## Generated Members: toString, equals, and hashCode

The primary distinction between data objects and regular objects lies in compiler-generated members. In [`compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/DataObjectContentChecker.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/DataObjectContentChecker.kt), the compiler enforces that data objects receive automatic implementations of standard data class methods.

For a regular object, you must manually override these methods:

```kotlin
object RegularConfig {
    val name = "default"
    
    override fun toString() = "RegularConfig(name=$name)"
    
    override fun equals(other: Any?) = other === this
    
    override fun hashCode() = System.identityHashCode(this)
}

```

A data object generates these automatically:

```kotlin
data object DataConfig {
    val name = "default"
}

// Compiler generates:
// toString() returns "DataConfig"
// equals() compares instance identity (always true for singleton)
// hashCode() based on instance identity

```

## Restrictions on Custom equals and hashCode

Unlike regular objects, data objects **cannot** override `equals()` or `hashCode()`. The `DataObjectContentChecker` in the Kotlin compiler explicitly prohibits these overrides to maintain consistent structural equality semantics.

If you attempt to override these methods in a data object, the compiler generates the error `DATA_OBJECT_CUSTOM_EQUALS_OR_HASH_CODE`:

```kotlin
// This code will fail to compile
data object InvalidObject {
    override fun equals(other: Any?) = true  // ERROR: DATA_OBJECT_CUSTOM_EQUALS_OR_HASH_CODE
    override fun hashCode() = 42           // ERROR: DATA_OBJECT_CUSTOM_EQUALS_OR_HASH_CODE
}

```

This restriction ensures that all data objects behave consistently regarding equality, relying on the compiler-generated implementations that treat the singleton instance as the sole representative of that type.

## Reflection and Type Identification

Data objects are identified as data classes through Kotlin reflection. According to [`libraries/stdlib/jvm/src/kotlin/reflect/KClass.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/jvm/src/kotlin/reflect/KClass.kt), the `isData` property returns `true` for data objects, allowing runtime identification of these special singletons.

```kotlin
data object SingletonData
object RegularSingleton

println(SingletonData::class.isData)    // true
println(RegularSingleton::class.isData)   // false

```

This distinction is crucial for serialization libraries, reflection-based frameworks, and generic code that needs to handle data classes and data objects uniformly while treating regular objects differently.

## Data Objects in Sealed Hierarchies

One of the most powerful applications of data objects appears in sealed class hierarchies. When used as variants of a sealed class or interface, data objects provide exhaustiveness checking in `when` expressions while maintaining the singleton semantics required for state representation.

The compiler test [`compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/exhaustiveWithNegativeSealedDataObjects.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/exhaustiveWithNegativeSealedDataObjects.kt) demonstrates how the Kotlin compiler treats data objects as complete sealed variants for exhaustiveness analysis.

```kotlin
sealed interface NetworkState
data object Loading : NetworkState
data object Error : NetworkState
data class Success(val data: String) : NetworkState

fun handleState(state: NetworkState) = when (state) {
    is Loading -> "Loading data..."
    is Error -> "An error occurred"
    is Success -> "Received: ${state.data}"
    // No else branch needed - exhaustive because Loading and Error are data objects
}

```

Unlike regular objects, data objects in sealed hierarchies signal to the compiler and readers that these variants represent distinct data states rather than behavioral implementations, improving type safety and code clarity.

## Language Feature Requirements

Data objects require explicit language feature support. According to [`compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt), the `DataObjects` language feature must be enabled for the compiler to recognize and process data object declarations.

This feature is available in Kotlin 1.9.0 and later versions. When using data objects in your projects, ensure your Kotlin compiler version supports this feature and that it is enabled in your build configuration.

## Summary

- **Data objects** automatically generate `toString()`, `equals()`, and `hashCode()` methods, while regular objects require manual implementation of these methods.
- The Kotlin compiler prohibits overriding `equals()` or `hashCode()` in data objects through [`DataObjectContentChecker.kt`](https://github.com/JetBrains/kotlin/blob/main/DataObjectContentChecker.kt) to maintain consistent equality semantics.
- Data objects return `true` for `KClass.isData` reflection checks, identifying them as data classes at runtime.
- When used in sealed hierarchies, data objects provide exhaustiveness checking in `when` expressions without requiring an `else` branch.
- The `DataObjects` language feature must be enabled in [`LanguageVersionSettings.kt`](https://github.com/JetBrains/kotlin/blob/main/LanguageVersionSettings.kt) to use this functionality, available from Kotlin 1.9.0 onwards.

## Frequently Asked Questions

### Can I add properties to a data object?

Yes, you can declare properties inside a data object just like a regular object. However, these properties do not participate in the generated `equals()` or `hashCode()` implementations because a data object is a singleton with only one instance. The generated `toString()` returns only the object name, not its property values, unlike data classes which include properties in their string representation.

### Why does my data object not show property values in toString()?

The Kotlin compiler generates a simplified `toString()` for data objects that returns only the object's name, as verified in [`compiler/testData/codegen/box/dataObjects/toString.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/testData/codegen/box/dataObjects/toString.kt). This differs from data classes, which generate `toString()` implementations that include all primary constructor properties. If you need property values in the string representation, you must override `toString()` manually, though you cannot override `equals()` or `hashCode()`.

### How do data objects improve sealed class hierarchies?

Data objects enhance sealed class hierarchies by providing clear, exhaustive variant representation without state variation. According to the exhaustiveness tests in [`compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/exhaustiveWithNegativeSealedDataObjects.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/exhaustiveWithNegativeSealedDataObjects.kt), the compiler recognizes data objects as complete sealed variants, enabling exhaustive `when` expressions without `else` branches. This improves type safety and makes the code's intent clearer—signaling that the variant represents a distinct state rather than a behavioral service.

### What happens if I try to override equals in a data object?

The Kotlin compiler will reject your code with the error `DATA_OBJECT_CUSTOM_EQUALS_OR_HASH_CODE`. The check is implemented in [`compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/DataObjectContentChecker.kt`](https://github.com/JetBrains/kotlin/blob/main/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/DataObjectContentChecker.kt), which explicitly prohibits custom `equals()` and `hashCode()` overrides in data objects. This restriction ensures that all data objects maintain consistent identity-based equality semantics, treating the singleton instance as the sole representative of that type.