All posts

Kotlin Production Best Practices

Essential Kotlin best practices for production including null safety, coroutine patterns, sealed classes for errors, and testing strategies.

Kotlin Production Best Practices

Kotlin's features eliminate entire categories of bugs when used correctly. Here are the practices that matter for production code.

Leverage Null Safety

// Don't fight the type system
fun processUser(user: User?): Result {
    val name = user?.name ?: return Result.Error("User required")
    val email = user.email ?: return Result.Error("Email required")
    return Result.Success(process(name, email))
}

// Avoid !! — it defeats the purpose of null safety
// Bad: user!!.name
// Good: user?.name ?: throw IllegalStateException("User cannot be null here")

Sealed Classes for Error Handling

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String, val cause: Throwable? = null) : Result<Nothing>()
}

fun getUser(id: String): Result<User> {
    return try {
        val user = repository.findById(id) ?: return Result.Error("User not found")
        Result.Success(user)
    } catch (e: Exception) {
        Result.Error("Database error", e)
    }
}

// Exhaustive when — compiler ensures all cases handled
when (val result = getUser("123")) {
    is Result.Success -> render(result.data)
    is Result.Error -> showError(result.message)
}

Coroutine Best Practices

// Use structured concurrency
coroutineScope {
    val user = async { userService.getUser(id) }
    val orders = async { orderService.getOrders(id) }
    ProfileResponse(user.await(), orders.await())
}

// Handle cancellation properly
suspend fun longRunningTask() {
    while (isActive) {  // Check for cancellation
        processNextBatch()
        yield()  // Cooperative cancellation point
    }
}

Data Classes for DTOs

data class CreateOrderRequest(
    val productId: String,
    val quantity: Int,
) {
    init {
        require(quantity > 0) { "Quantity must be positive" }
        require(productId.isNotBlank()) { "Product ID required" }
    }
}

Extension Functions for Clean APIs

Keep extension functions focused and discoverable. Avoid creating extensions on common types like String or Int that might conflict.

Monitoring

Kotlin's coroutines can swallow exceptions silently in fire-and-forget launches. Use Bugsly to catch these hidden failures and ensure your CoroutineExceptionHandler actually reports errors rather than just logging them.

Try Bugsly Free

AI-powered error tracking that explains your bugs. Set up in 2 minutes, free forever for small projects.

Get Started Free