The Kotlin Contract API: Teaching the Compiler to Understand Your Code

 Unlocking advanced smart-casts and definite assignment analysis by making formal promises to the Kotlin compiler.

The Kotlin Contract API: Teaching the Compiler to Understand Your Code

Previous Part: Part 4: Kotlin Metadata: How Reflection Tools “Read” Your Kotlin Code

We’ve journeyed through Kotlin’s internals, from lazy delegates to hidden metadata. Today, we delve into a feature that bridges the gap between what you know about your code and what the compiler understands: the Contract API.

Have you ever written a utility function that performs a null check, then found yourself annoyed that the compiler didn’t “realize” the variable was non-null after your function call?

fun ensureNotNull(value: String?) {
if (value == null) {
throw IllegalArgumentException("Value must not be null")
}
}

fun processData(data: String?) {
ensureNotNull(data)
// Error: 'data' is still seen as nullable here
println(data.length) // !! Error: Only safe (?) or non-null asserted (!!) calls allowed
}

This happens because Kotlin’s Data Flow Analysis is intra-procedural — it doesn’t “look inside” other function bodies. This is where the Contract API shines.

1. The Core Problem: Compiler Blind Spots

The Kotlin compiler is brilliant within a single scope, but it has a fundamental boundary: it doesn’t infer guarantees from arbitrary function calls. This keeps compilation fast, but it creates “blind spots” when logic is extracted into helper functions.

2. Introducing kotlin.contracts.contract { ... }

The Contract API allows you to send a direct message to the compiler about your function’s guarantees.

Note: Contracts currently only work with inline functions because the compiler needs to "inline" these guarantees directly into the caller's scope to update its data-flow analysis.

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
inline fun <T> ensureNotNull(value: T?) {
contract {
// "If this function finishes successfully, 'value' is non-null"
returns() implies (value != null)
}
if (value == null) throw IllegalArgumentException("Null value")
}

3. Common Contract Types

a) callsInPlace (The DSL Hero)

This tells the compiler exactly how many times a lambda will be called. Without it, you cannot initialize val properties inside lambdas because the compiler fears re-assignment or non-assignment.

@OptIn(ExperimentalContracts::class)
inline fun runOnce(block: () -> Unit) {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
block()
}

fun setup() {
val myValue: String
runOnce { myValue = "Initialized" }
println(myValue) // No error!
}

b) returnsNotNull (For Nullable Return Types)

While returns() works for functions returning UnitreturnsNotNull() is specifically for functions whose return type is nullable. It says: "If I return anything that isn't null, then this parameter must be non-null."

@OptIn(ExperimentalContracts::class)
inline fun parseId(s: String?): Int? {
contract {
returnsNotNull() implies (s != null)
}
return s?.toIntOrNull()
}

c) returns(value) (For Boolean Checks)

Used when a function returns a specific value (like true or false) to indicate a state change.

@OptIn(ExperimentalContracts::class)
inline fun String?.isValid(): Boolean {
contract {
returns(true) implies (this@isValid != null)
}
return this != null && this.isNotBlank()
}

4. A Promise, Not a Proof

It is vital to remember: The compiler does not verify your contract. If you write a contract that says returns() implies (value != null) but then return normally with a null value, the compiler will blindly trust you. This leads to the most dangerous kind of bug: a NullPointerException on a variable the compiler thinks is non-null.

Rule of Thumb: If a library (like Kotlin’s standard require() or check()) understands Kotlin features better than standard Java reflection does, it is likely using a built-in Contract.

5. Summary of the “Contract”


Summary of the _Contract_

🙋‍♂️ Frequently Asked Questions (FAQs)

Are Contracts supported in KMP?

Yes. Contracts are a compiler-level feature working across JVM, JS, and Native targets.

Do Contracts affect performance?

No. They are a compile-time directive. They don’t generate extra bytecode at runtime beyond the standard inline function mechanics.

Why is it still “Experimental”?

While stable in practice (and used heavily in the Kotlin StdLib), the JetBrains team is keeping the @ExperimentalContracts tag while they refine the syntax for the K2 compiler.

💬 Join the Conversation!

  • Have you ever “lied” to the compiler with a contract and paid the price with a runtime crash?
  • What is one utility function in your project that currently requires an annoying !! that a contract could fix?

This concludes our “Under the Hood” series! We’ve covered Delegated Properties, Recomposition, Value Classes, Metadata, and now Contracts.

📘 Master Your Next Technical Interview

Since Java is the foundation of Android development, mastering DSA is essential. I highly recommend “Mastering Data Structures & Algorithms in Java”. It’s a focused roadmap covering 100+ coding challenges to help you ace your technical rounds.

Comments

Popular posts from this blog

No More _state + state: Simplifying ViewModels with Kotlin 2.3

Why You Should Stop Passing ViewModels Around Your Compose UI Tree 🚫

Is Jetpack Compose Making Your APK Fatter? (And How to Fix It)