Kotlin Error Handling Patterns
Comprehensive error handling: sealed result types, validated aggregation, and railway-oriented programming.
// Validated: collect ALL errors instead of failing on first
data class Validated<out E, out A>(
val value: A?,
val errors: List<E>
) {
val isValid get() = errors.isEmpty()
companion object {
fun <E, A> valid(value: A) = Validated<E, A>(value, emptyList())
fun <E, A> invalid(errors: List<E>) = Validated<E, A>(null, errors)
fun <E, A> invalid(error: E) = Validated<E, A>(null, listOf(error))
}
}
fun <E, A, B> Validated<E, A>.map(f: (A) -> B): Validated<E, B> =
if (isValid) Validated.valid(f(value!!)) else Validated.invalid(errors)
// Combine multiple validations
fun <E, A, B, C> zip(
v1: Validated<E, A>,
v2: Validated<E, B>,
f: (A, B) -> C
): Validated<E, C> {
val allErrors = v1.errors + v2.errors
return if (allErrors.isEmpty()) Validated.valid(f(v1.value!!, v2.value!!))
else Validated.invalid(allErrors)
}
// Validation rules
typealias ValidationError = String
fun validateName(name: String): Validated<ValidationError, String> =
if (name.isNotBlank() && name.length in 2..50)
Validated.valid(name.trim())
else Validated.invalid("Name must be 2-50 characters")
fun validateEmail(email: String): Validated<ValidationError, String> =
if (email.matches(Regex(".+@.+\\..+")))
Validated.valid(email.lowercase().trim())
else Validated.invalid("Invalid email format")
fun validateAge(age: Int): Validated<ValidationError, Int> {
val errors = mutableListOf<ValidationError>()
if (age < 0) errors.add("Age must be positive")
if (age > 150) errors.add("Age must be realistic")
return if (errors.isEmpty()) Validated.valid(age)
else Validated.invalid(errors)
}
data class UserRegistration(val name: String, val email: String, val age: Int)
fun validateRegistration(
name: String, email: String, age: Int
): Validated<ValidationError, UserRegistration> {
val vName = validateName(name)
val vEmail = validateEmail(email)
val vAge = validateAge(age)
val allErrors = vName.errors + vEmail.errors + vAge.errors
return if (allErrors.isEmpty())
Validated.valid(UserRegistration(vName.value!!, vEmail.value!!, vAge.value!!))
else Validated.invalid(allErrors)
}
// Kotlin built-in Result chaining
fun parseAndValidate(input: Map<String, String>): Result<UserRegistration> = runCatching {
val name = requireNotNull(input["name"]) { "name is required" }
val email = requireNotNull(input["email"]) { "email is required" }
val age = requireNotNull(input["age"]?.toIntOrNull()) { "valid age is required" }
require(name.isNotBlank()) { "name must not be blank" }
require(email.contains("@")) { "invalid email" }
require(age in 0..150) { "invalid age" }
UserRegistration(name, email, age)
}
fun main() {
// Validated: collects all errors
val valid = validateRegistration("Alice", "alice@test.com", 30)
println("Valid: $valid")
val invalid = validateRegistration("", "bad-email", -5)
println("Invalid: $invalid")
println("Errors: ${invalid.errors}")
// Result chaining
val good = parseAndValidate(mapOf("name" to "Bob", "email" to "bob@t.com", "age" to "25"))
val bad = parseAndValidate(mapOf("email" to "nope"))
println("Good: ${good.getOrNull()}")
println("Bad: ${bad.exceptionOrNull()?.message}")
// runCatching chain
val result = runCatching { "42" }
.mapCatching { it.toInt() }
.mapCatching { require(it > 0) { "Must be positive" }; it }
.map { it * 2 }
.getOrElse { 0 }
println("Chained: $result")
}Use Cases
- Form validation with error accumulation
- Input parsing with comprehensive error messages
- Railway-oriented business logic
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Preconditions — require, check, and error
Validate inputs and state with Kotlin preconditions: require, check, error, and custom assertions.
Best for: Input validation at function boundaries
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
Enum Classes — Advanced Patterns
Use Kotlin enum classes with properties, methods, interfaces, and companion utilities.
Best for: Type-safe constant sets with behavior