skip to content

Why can you use a variable as non-null after calling requireNotNull(x) or checkNotNull(x), even though the null check happens inside a separate function?

level: juniorimportance: must knowfreq 55%

answer

  1. contract { returns() implies (value != null) }
  2. requireNotNull -> IllegalArgumentException (inputs)
  3. checkNotNull -> IllegalStateException (state)
  4. Both return the non-null value
  5. Smart cast survives past the call

basics

~20 s

Those standard functions tell the compiler a fact: if they return normally, the argument is not null. So after the call the compiler treats the variable as non-null and lets you use it without ?. or !!.

solid answer

~40 s

requireNotNull and checkNotNull are declared with a Kotlin contract. A contract is a compiler-readable promise attached to a function. Theirs says returns() implies (value != null): if the function returns normally (doesn't throw), the argument was non-null. The compiler reads that and smart-casts the variable from T? to T past the call site, so you can call members directly without ?., !!, or a local val. requireNotNull throws IllegalArgumentException on null (precondition on input); checkNotNull throws IllegalStateException (invariant on state). Both also return the non-null value, so val nn = requireNotNull(x) works too. Without the contract the compiler couldn't know the function guaranteed non-nullity and would still force null handling afterward.

code

kotlin · 5 lines
kotlin
fun greet(name: String?) {
    requireNotNull(name) { "name must be provided" }
    // name is smart-cast to String from here on
    println("Hello, ${name.uppercase()}")
}

go deeper

for a junior

Knows that after requireNotNull/checkNotNull the variable is usable as non-null and you don't need !!.

for a middle

Explains the contract returns() implies (value != null) and the IllegalArgumentException vs IllegalStateException distinction.

for a senior

Discusses fail-fast intent, choosing these over !! for clearer exceptions, and that the return value is the non-null type.

for a principal

Frames contract helpers as part of a nullability-validation policy and when fail-fast vs graceful (?.) is the right boundary behavior.

## The problem Kotlin's compiler tracks nullability. After an inline `if (x != null)` check it performs a **smart cast**: inside that branch `x: String?` is usable as `String`. But when the null check lives inside *another function*, the compiler normally can't see it — a function call is opaque. ```kotlin fun handle(name: String?) { // without help, compiler still thinks name is String? requireNotNull(name) println(name.length) // works: smart-cast to String here } ``` ## How the standard library makes this work: contracts A **contract** is metadata attached to a function that describes its effects in a form the compiler understands. `requireNotNull` and `checkNotNull` are declared (roughly) like: ```kotlin public inline fun <T : Any> requireNotNull(value: T?): T { contract { returns() implies (value != null) } return value ?: throw IllegalArgumentException("Required value was null.") } ``` - `contract { ... }` is the block (from `kotlin.contracts`) where effects are declared. It must be the first statement. - `returns()` means "the function returns normally" (no exception thrown). - `implies (value != null)` states the condition that holds *given* a normal return. So the compiler reasons: *the line after the call executed, therefore the function returned normally, therefore `value != null`* — and smart-casts the argument to its non-null type. ## requireNotNull vs checkNotNull - `requireNotNull(x)` throws **IllegalArgumentException** — use it to validate **inputs / arguments**. - `checkNotNull(x)` throws **IllegalStateException** — use it to validate **internal state / invariants**. - Both **return** the non-null value, so `val nn = checkNotNull(x)` is also valid. ## Contrast with !! and ?. - `x!!` throws `NullPointerException` and gives no descriptive message; the contract-based helpers throw clearer exceptions and read as intent. - `x?.foo()` skips when null instead of asserting non-null. Use the contract helpers when null is genuinely a bug you want to fail fast on with a clear message.

  • What is the difference between requireNotNull and checkNotNull?
    Same contract and smart-cast behavior; requireNotNull throws IllegalArgumentException (argument/precondition), checkNotNull throws IllegalStateException (internal-state/invariant).
  • Does requireNotNull return anything useful?
    Yes — it returns the non-null value, so you can write val nn = requireNotNull(x) and bind the smart-cast result to a new val.

Like a signed receipt: the function hands back proof 'this wasn't null', and the compiler trusts the receipt for the rest of the scope.

saying these in an interview costs you the question

  • Saying it's the same as x!! (different exception, no contract semantics, no message)
  • Claiming the compiler 'looks inside' the function body — it reads the contract, not the code
  • Mixing up which one throws IllegalArgumentException vs IllegalStateException
  • Thinking the smart cast only applies inside the function, not after the call

context