Exception As Object

ID

kotlin.exception_as_object

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Kotlin

Tags

best-practice, reliability

Description

Reports object declarations whose supertype is a Throwable subclass. A singleton exception cannot carry per-throw state — its stack trace, cause, and message are fixed forever to whatever was set when the object was first initialised.

Rationale

object NotFound : RuntimeException("not found")   // FLAW

fun lookupA() = throw NotFound      // stack trace is from the initial creation
fun lookupB() = throw NotFound      // same stack trace — no clue where this came from

Throwable.fillInStackTrace() runs in the exception’s constructor; a singleton runs that constructor exactly once, so every throw site logs the trace recorded the first time anyone touched the object. Crash reports become useless. Cause chains share one storage slot, and writes from one throw site overwrite anything an earlier throw recorded.

Remediation

Declare the exception as a normal class and instantiate it at every throw site:

class NotFoundException(message: String) : RuntimeException(message)

fun lookupA() = throw NotFoundException("a")
fun lookupB() = throw NotFoundException("b")

If the exception holds no state and represents an enumeration of failure modes, prefer a sealed class hierarchy of regular classes over an object chain.