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.