Kotlin's New Silent Bug Killer: The Unused Return Value Checker
You know that sinking feeling when you realize a function has been returning a value for weeks, and you've been ignoring it the whole time? Yeah, we've all been there. Kotlin 2.3.0 just introduced a feature that'll save you from these embarrassing bugs: the Unused Return Value Checker.
The Bug That Got Away
Picture this: You're refactoring some string formatting logic. Everything compiles. Tests pass (or so you think). Code review approved. Shipped to production. And then... nothing works as expected.
fun formatGreeting(name: String): String {
if (name.isBlank()) return "Hello, anonymous user!"
if (!name.contains(' ')) {
// Oops! Creating a string but not returning it
"Hello, " + name.replaceFirstChar(Char::titlecase) + "!"
}
val (first, last) = name.split(' ')
return "Hello, $first! Or should I call you Dr. $last?"
}
Can you spot the bug? There's a string being created in the second if block, but it's never used. The function just falls through to the next statement. Classic mistake, and surprisingly easy to miss.
Enter the Unused Return Value Checker
Kotlin 2.3.0's new checker catches exactly this kind of bug. It warns you whenever:
- A function returns a value (anything except
UnitorNothing) - That value isn't used in any way
- It's just... discarded into the void
Think of it as a safety net for your brain's inevitable oversight moments.
How It Works
The checker is smart about what it flags. It looks for expressions that produce meaningful results that get silently dropped:
fun processData(input: String): Result<Data> {
validateInput(input) // ⚠️ Warning: Result is ignored!
val data = parseData(input)
transformData(data) // ⚠️ Warning: Transformed data is ignored!
return data
}
Both validateInput() and transformData() return values, but we're not using them. The checker will call this out.
Real-World Examples
Example 1: Collection Operations
fun addItemToCart(item: Item, cart: MutableList<Item>) {
cart.add(item) // ⚠️ Warning: Returns Boolean indicating success
// Did the add actually succeed? You'll never know!
}
// Better:
fun addItemToCart(item: Item, cart: MutableList<Item>) {
val success = cart.add(item)
if (!success) {
logger.warn("Failed to add item: $item")
}
}
// Or if you really don't care:
fun addItemToCart(item: Item, cart: MutableList<Item>) {
val _ = cart.add(item) // Explicitly ignored
}
Example 2: Result Types
class UserService {
fun updateUser(user: User): Result<Unit> {
// ... validation logic
return Result.success(Unit)
}
}
fun handleUserUpdate(user: User, service: UserService) {
service.updateUser(user) // ⚠️ Warning: Result not checked!
// Code continues as if everything worked...
showSuccessMessage() // This might be a lie!
}
// Better:
fun handleUserUpdate(user: User, service: UserService) {
service.updateUser(user)
.onSuccess { showSuccessMessage() }
.onFailure { showErrorMessage(it) }
}
Example 3: String Operations
fun sanitizeInput(input: String): String {
input.trim() // ⚠️ Returns new string, original unchanged
input.lowercase() // ⚠️ Returns new string, original unchanged
return input // Still returns the original, unsanitized input!
}
// Correct:
fun sanitizeInput(input: String): String {
return input.trim().lowercase()
}
This one is particularly sneaky because strings are immutable in Kotlin. You must use the return value.
Enabling the Checker
Add this to your build.gradle.kts:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xreturn-value-checker=check")
}
}
This enables checking for functions marked with @MustUseReturnValues (which includes most of Kotlin's standard library).
Full Project Coverage
Want to check everything in your project? Use the nuclear option:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xreturn-value-checker=full")
}
}
Now every function's return value in your project gets checked. Brace yourself for warnings! 😅
Marking Your Own Functions
You can mark your functions to enforce return value usage:
@MustUseReturnValues
class PaymentService {
fun processPayment(amount: Double): TransactionResult {
// Implementation
}
fun refund(transactionId: String): RefundResult {
// Implementation
}
}
Now anyone ignoring these return values gets a compiler warning. Perfect for critical operations where ignoring the result could lead to bugs.
File-Level Annotation
Mark an entire file:
@file:MustUseReturnValues
package com.example.critical
// All functions in this file now require their returns to be used
fun validatePayment(): Boolean { /* ... */ }
fun processOrder(): OrderResult { /* ... */ }
When You Actually Want to Ignore a Return Value
Sometimes you genuinely don't care about a return value. The checker gives you two options:
1. Mark the Function as Ignorable
@IgnorableReturnValue
fun <T> MutableList<T>.addAndIgnoreResult(element: T): Boolean {
return add(element)
}
// Now this is fine:
list.addAndIgnoreResult(item) // No warning
2. Use the Special Underscore Variable
fun main() {
// This produces a warning:
computeExpensiveValue()
// This suppresses it for this specific call site:
val _ = computeExpensiveValue()
}
The val _ idiom explicitly says "I know this returns something, and I'm consciously ignoring it."
Common Patterns This Catches
Builder Pattern Mistakes
fun configureServer(): Server {
ServerBuilder()
.setPort(8080)
.setHost("localhost")
.setTimeout(30.seconds)
// ⚠️ Warning: Builder not used!
// Missing .build() !
return Server.default()
}
Forgetting to Use Mapped Values
val users = getUsers()
users.map { it.name.uppercase() } // ⚠️ Warning: New list ignored!
println(users) // Still has lowercase names!
// Should be:
val uppercaseNames = users.map { it.name.uppercase() }
println(uppercaseNames)
Stream/Sequence Operations
fun processLogs(logs: Sequence<String>) {
logs
.filter { it.contains("ERROR") }
.map { parseError(it) }
// ⚠️ Warning: Sequence not consumed!
// Nothing actually happens until you call a terminal operation!
}
// Correct:
fun processLogs(logs: Sequence<String>) {
logs
.filter { it.contains("ERROR") }
.map { parseError(it) }
.toList() // Actually consume the sequence
}
Configuration Recommendations
For existing projects: Start with -Xreturn-value-checker=check. Fix warnings gradually.
For new projects: Go straight to -Xreturn-value-checker=full. Catch bugs before they happen.
For libraries: Annotate your public API with @MustUseReturnValues for critical functions. Your users will thank you.
The Bottom Line
The Unused Return Value Checker is like having a paranoid code reviewer who never sleeps. It catches subtle bugs that slip past humans, especially during refactoring or late-night coding sessions.
This isn't just about catching typos. It's about enforcing intent. When a function returns something, it usually means that something matters. If you're ignoring it, either:
- You have a bug (most likely), or
- The function shouldn't return that value in the first place
Either way, the checker makes you face the question explicitly.
Try It Today
Enable it in your project and run a build. You might be surprised by what it finds. I was.
In one of my projects, it caught:
- 3 unchecked
Resulttypes - 2 string operations that weren't being used
- 1 critical builder pattern where I forgot
.build()
All bugs that would have made it to production without this feature.
Give your code the safety net it deserves. Enable the Unused Return Value Checker and sleep better at night knowing your functions' return values aren't screaming into the void, unheard.
Quick Reference
// Enable for standard library checks
freeCompilerArgs.add("-Xreturn-value-checker=check")
// Enable for entire project
freeCompilerArgs.add("-Xreturn-value-checker=full")
// Mark your critical functions
@MustUseReturnValues
fun criticalOperation(): Result<Unit>
// Mark functions where ignoring is okay
@IgnorableReturnValue
fun logMessage(msg: String): Boolean
// Explicitly ignore at call site
val _ = someFunction()
What bugs has this checker caught in your code? Share your stories in the comments! And if you find this helpful, consider enabling it in your team's projects – your teammates will appreciate the extra safety.
Happy (bug-free) coding! 🐛🔨
Need an Android Developer or a full-stack website developer?
I specialize in Kotlin, Jetpack Compose, and Material Design 3. For websites, I use modern web technologies to create responsive and user-friendly experiences. Check out my portfolio or get in touch to discuss your project.


