Exception Raised Unexpected

ID

kotlin.exception_raised_unexpected

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Kotlin

Tags

best-practice, reliability

Description

Reports override fun toString(), override fun hashCode() and override fun equals(other: Any?) implementations whose body raises an exception. These three methods are inherited from Any and are invoked by an unpredictable set of callers — debuggers, IDE tooltips, logging frameworks, hash-based collections — none of which expect them to fail.

Rationale

A toString() that throws makes a value invisible in the debugger: hovering over it crashes the inspector. A hashCode() that throws prevents the object from ever being placed into a HashMap or HashSet. An equals() that throws breaks contains, removeAll, and almost every list/set algorithm in the standard library.

class Bad(val id: Int) {
    override fun toString(): String {
        throw IllegalStateException("not initialised")   // FLAW
    }
    override fun hashCode(): Int = throw RuntimeException("boom")  // FLAW
}

The methods are also called from places that have no clean way to recover — a logging call cannot reasonably catch and swallow the exception, so the failure surfaces as a noisy stack trace in a log line and the original work is lost.

Remediation

Implement these methods so they always return a value. If a field can legitimately be uninitialised, return a placeholder such as "<uninitialised>" from toString() and 0 from hashCode(). Reserve exceptions for methods the contract allows to throw.

class Good(val id: Int) {
    override fun toString(): String = "Good(id=$id)"
    override fun hashCode(): Int = id
    override fun equals(other: Any?): Boolean = other is Good && other.id == id
}