Data Class With Array Property Missing Overrides

ID

kotlin.equals_method_overridden_data_classes

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

Kotlin

Tags

best-practice, reliability

Description

Reports a data class with an array-typed property in its primary constructor — the generic Array<T> or any specialised primitive array such as ByteArray or IntArray — that does not override equals, hashCode and toString.

Only primary-constructor properties count, because those are exactly the properties the compiler puts into the generated members. A property declared in the class body takes no part in the generated equals, so an array there is not this defect. A property whose type merely contains an array (List<ByteArray>) is not itself an array and is not considered.

Rationale

A data class generates equals, hashCode and toString from its primary-constructor properties — and for an array property those generated members fall back to identity, because Array.equals is reference equality. Two instances holding arrays with the same elements compare unequal, hashCode() differs between otherwise-identical instances, and toString() prints something like [B@1a2b3c instead of the elements.

// FLAW -- payload is compared, hashed and printed by reference
data class Measurement(val subject: String, val payload: ByteArray)

val a = Measurement("temp", byteArrayOf(1, 2))
val b = Measurement("temp", byteArrayOf(1, 2))
a == b            // false
a.hashCode() == b.hashCode()  // false
println(a)        // Measurement(subject=temp, payload=[B@1b6d3586)

This bites hardest where a data class is used as a map key, put in a Set, or compared in a test assertion — all three of which a data class is otherwise the natural choice for.

Remediation

Override all three members and delegate to the content-based array operations (contentEquals, contentHashCode, contentToString; the contentDeep* variants for nested arrays):

data class Measurement(val subject: String, val payload: ByteArray) {

    override fun equals(other: Any?): Boolean {
        if (other !is Measurement) {
            return false
        }

        return subject == other.subject && payload.contentEquals(other.payload)
    }

    override fun hashCode(): Int {
        return 31 * subject.hashCode() + payload.contentHashCode()
    }

    override fun toString(): String {
        return "Measurement(subject=$subject, payload=${payload.contentToString()})"
    }
}

Where the property does not have to be an array, the simpler fix is to change its type: a List<Byte> compares, hashes and prints by content, so the generated members are then correct as they stand.

© 2026 Xygeni. All rights reserved