Private Data Class Copy
ID |
kotlin.private_data_class_copy |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Kotlin |
Tags |
best-practice, reliability |
Description
Reports a data class declared with a private constructor. The compiler
generates a copy() member whose visibility matches the class, not the
primary constructor. Making the constructor private therefore only stops
direct instantiation — clients can still produce new instances via copy(),
bypassing the intended restriction.
Rationale
data class User private constructor(val id: Int) {
companion object { fun create(id: Int) = User(id) }
}
fun caller(u: User) {
val other = u.copy(id = 99) // public copy() side-steps `private constructor`
}
The private constructor is usually applied to enforce invariants — only the
companion factory should produce valid instances. The auto-generated copy()
quietly defeats that goal: any caller holding an existing instance can
fabricate a fresh one with arbitrary field values.
Remediation
// Option 1: drop `data` and write equality manually
class User private constructor(val id: Int) {
companion object { fun create(id: Int) = User(id) }
// implement equals/hashCode/toString as needed
}
// Option 2: keep `data` but make the class itself non-public
internal data class User(val id: Int)
Either remove the data modifier and write the desired override manually, or
make the data class itself internal / private so that copy() cannot be
called by external code. From Kotlin 2.0 onward, exposing copy() from a
non-public primary constructor will require an explicit opt-in.