Monday, September 14, 2026

Kotlin Method Chaining Overuse

At my last job, there was a lot of Kotlin code that overused method chaining. Many classes contained blocks of code where a nullable object was operated on and transformed, over and over, in a single fluent expression using a lot of scope functions. The code would look something like this:


fun processOrder(orderId: String?) {
    orderId?.takeIf { it.isNotBlank() }
    ?.toIntOrNull()
    ?.let { httpClient.getPartialOrder(it) }
    ?.toDomainObject()
    ?.let { dbClient.findFullOrder(it) }
    ?.takeIf { it.status == Status.PENDING }
    ?.apply { logger.info("Processing order $id") }
    ?.let { dbClient.updateStatus(it.id, Status.PROCESSING) }
    ?.apply { this.total = this.price + billingService.calculateTax(this) }
    ?.also { order -> emailService.sendConfirmation(order) }
    ?.let { dbClient.finalizeOrder(it) }
    ?: logger.error("Order processing skipped or invalid ID")
}

This is a problem. Yes, the code works, it's arguably kotlinic, and it's brief. A strong Kotlin programmer will understand what is occurring at each line. However, it's unintuitive, especially to someone new to Kotlin. Even a senior Kotlin coder might require context to understand each line, especially if the variables and methods aren't named well.

Without Googling, try to remember which scope function returns its lambda result and which returns the original context object? At what point is the variable referred to as it a String, an Int, a FullOrder, or a database-friendly FullOrderDomain? What is even the purpose of cramming all of this logic into just one expression?

Just because you can doesn't mean you should. Let's look at each problem, line by line.