Preconditions — require, check, and error
Validate inputs and state with Kotlin preconditions: require, check, error, and custom assertions.
class BankAccount private constructor(
val accountNumber: String,
val owner: String,
private var _balance: Double
) {
val balance: Double get() = _balance
companion object {
fun create(accountNumber: String, owner: String, initialBalance: Double = 0.0): BankAccount {
// require — validates function arguments (throws IllegalArgumentException)
require(accountNumber.isNotBlank()) { "Account number cannot be blank" }
require(accountNumber.length == 10) { "Account number must be 10 chars, got ${accountNumber.length}" }
require(owner.isNotBlank()) { "Owner name required" }
require(initialBalance >= 0) { "Initial balance cannot be negative: $initialBalance" }
return BankAccount(accountNumber, owner, initialBalance)
}
}
fun deposit(amount: Double) {
require(amount > 0) { "Deposit amount must be positive: $amount" }
_balance += amount
}
fun withdraw(amount: Double) {
require(amount > 0) { "Withdrawal amount must be positive: $amount" }
// check — validates object state (throws IllegalStateException)
check(_balance >= amount) { "Insufficient funds: balance=$_balance, requested=$amount" }
_balance -= amount
}
fun transfer(to: BankAccount, amount: Double) {
require(amount > 0) { "Transfer amount must be positive" }
check(this !== to) { "Cannot transfer to same account" }
check(_balance >= amount) { "Insufficient funds for transfer" }
_balance -= amount
to._balance += amount
}
override fun toString() = "$owner ($accountNumber): \$${"%,.2f".format(_balance)}"
}
// Custom assertion helpers
fun requireEmail(email: String): String {
require(email.contains("@")) { "Invalid email format: $email" }
require(email.length <= 255) { "Email too long: ${email.length} chars" }
return email
}
fun <T> requireNotEmpty(collection: Collection<T>, name: String = "collection"): Collection<T> {
require(collection.isNotEmpty()) { "$name must not be empty" }
return collection
}
// checkNotNull — runtime null check (throws IllegalStateException)
fun processConfig() {
val host: String? = System.getenv("DB_HOST")
// val dbHost = checkNotNull(host) { "DB_HOST environment variable must be set" }
// use dbHost...
}
// error() — always throws IllegalStateException
fun statusMessage(code: Int): String = when (code) {
200 -> "OK"
404 -> "Not Found"
500 -> "Server Error"
else -> error("Unknown status code: $code")
}
fun main() {
// Normal flow
val account = BankAccount.create("1234567890", "Alice", 1000.0)
println(account)
account.deposit(500.0)
println("After deposit: $account")
account.withdraw(200.0)
println("After withdraw: $account")
// Validation failures
println("\n--- Validation failures ---")
listOf(
{ BankAccount.create("", "Test") },
{ BankAccount.create("123", "Test") },
{ BankAccount.create("1234567890", "Test", -100.0) },
{ account.withdraw(99999.0) },
{ statusMessage(999) }
).forEach { action ->
try {
action()
} catch (e: IllegalArgumentException) {
println("Argument error: ${e.message}")
} catch (e: IllegalStateException) {
println("State error: ${e.message}")
}
}
// requireNotNull for cascading
val name: String? = "Alice"
val validName = requireNotNull(name) { "Name must not be null" }
println("\nValid name: $validName (length: ${validName.length})")
}Use Cases
- Input validation at function boundaries
- State machine invariant checks
- Defensive programming for public APIs
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Kotlin Error Handling Patterns
Comprehensive error handling: sealed result types, validated aggregation, and railway-oriented programming.
Best for: Form validation with error accumulation
Result Monad — Functional Error Handling
Handle errors functionally with Kotlin Result: map, recover, fold, and chaining fallible operations.
Best for: Type-safe error handling without exceptions
Coroutine Exception Handling and Supervision
Handle errors in coroutines: CoroutineExceptionHandler, supervisorScope, try-catch, and error propagation.
Best for: Robust error handling in concurrent operations
Functional Error Handling with Either
Use Either and Option for type-safe error handling: Railway-oriented programming without exceptions.
Best for: Type-safe error handling without exceptions