skip to content

At a Java boundary, what is the difference between guarding a platform value with requireNotNull/checkNotNull versus using the !! operator, and when should each be used?

level: middleimportance: must knowfreq 55%

answer

  1. !! -> bare NPE, no message
  2. requireNotNull -> IllegalArgumentException + message
  3. checkNotNull -> IllegalStateException + message
  4. All smart-cast to non-null
  5. Best option: ?: / ?. to avoid asserting

basics

~10 s

All of them crash if the value is null, but requireNotNull/checkNotNull let you attach a clear message and throw a meaningful exception, while !! throws a bare NullPointerException with no explanation.

solid answer

~40 s

`!!` (the not-null assertion operator) converts `T?`/platform `T!` to `T` and throws a bare `NullPointerException` (actually `KotlinNullPointerException`) if the value is null — no message, no intent. `requireNotNull(x)` and `checkNotNull(x)` also assert non-null and smart-cast `x` to non-null, but throw `IllegalArgumentException` and `IllegalStateException` respectively, accept a lazily-evaluated message lambda, and communicate intent: `require` for validating inputs/arguments, `check` for validating internal state/invariants. At a Java boundary you generally want `requireNotNull` with a message ("name missing from repo") so the failure is self-documenting and located at the edge. Reserve `!!` for the rare case where null is genuinely impossible and a terse assertion suffices, or in throwaway code. Best of all is often avoiding the assertion entirely with `?:`/`?.`/`filterNotNull`.

code

kotlin · 3 lines
kotlin
val a = repo.findName()!!                                   // bare NPE
val b = requireNotNull(repo.findName()) { "name missing" }  // IAE + message
val c = repo.findName() ?: "anonymous"                      // no throw

go deeper

for a junior

Knows !! crashes without a message and requireNotNull gives a clearer error.

for a middle

Distinguishes the three exception types, the lazy message lambda, and that all smart-cast to non-null.

for a senior

Chooses require vs check by intent and prefers ?:/?. when null is a valid case at the boundary.

for a principal

Standardizes boundary guards (requireNotNull + message), bans casual !!, and ties it to diagnosability/observability.

## The three assertion tools All three take a possibly-null value (including a platform type) and either return it as non-null or throw. ### `!!` — the not-null assertion operator ```kotlin val name: String = repo.findName()!! ``` - Converts `String?` / `String!` to `String`. - If null, throws a bare `NullPointerException` (the Kotlin runtime type `KotlinNullPointerException`) with **no message**. - Terse but uninformative; a stack trace pointing at `!!` is all you get. ### `requireNotNull(value) { message }` ```kotlin val name = requireNotNull(repo.findName()) { "name missing for id=$id" } ``` - Returns the value **smart-cast to non-null** (`name: String`). - Throws **`IllegalArgumentException`** if null. - Message lambda is **evaluated lazily** (only on failure), so no cost on the happy path. - Semantics: *"this argument/input must not be null."* ### `checkNotNull(value) { message }` - Identical mechanics but throws **`IllegalStateException`**. - Semantics: *"this object/state invariant must hold."* ## Choosing at a Java boundary | Tool | Throws | Message | Use for | |------|--------|---------|---------| | `!!` | NPE (bare) | none | impossible-null, throwaway | | `requireNotNull` | IllegalArgumentException | yes (lazy) | validating incoming Java values/args | | `checkNotNull` | IllegalStateException | yes (lazy) | validating internal invariants | For a platform value crossing from Java, **`requireNotNull` with a message** is usually best: it documents the assumption, fails fast at the edge, and yields a diagnosable exception instead of a cryptic NPE deep in the call chain. Pair it with the choice of an explicit boundary type. ## Often, assert nothing The strongest option is to not assert at all and handle null: ```kotlin val name = repo.findName() ?: "anonymous" // Elvis default repo.findName()?.let { use(it) } // safe-call ``` Use an assertion only when null truly indicates a bug. ## Common confusion `requireNotNull`/`checkNotNull` are **not** the same as `require(x != null)` followed by `x!!` — they return the smart-cast value directly, so you keep a non-null binding without a second deref.

  • Why is the message argument to requireNotNull a lambda rather than a plain String?
    So it is evaluated lazily — only when the check fails — avoiding string-building cost on the common non-null path.
  • Does requireNotNull smart-cast the value, or do you still need !! afterward?
    It returns the value smart-cast to non-null, so `val x = requireNotNull(y)` gives a non-null `x` directly; no `!!` needed.

saying these in an interview costs you the question

  • Saying `!!` throws an IllegalStateException
  • Believing requireNotNull and checkNotNull throw the same exception type
  • Sprinkling `!!` across a Java boundary as the default
  • Not knowing the message lambda is lazily evaluated
  • Thinking these don't smart-cast and you must `!!` again after

context