At the Java interop boundary, when do you use requireNotNull versus checkNotNull, and how do they differ from the !! operator?
answer
- require -> IllegalArgumentException -> bad input
- check -> IllegalStateException -> bad state/invariant
- Both return the value AND smart-cast
- !! -> NullPointerException, no message, no intent
- Lazy message lambda, inline functions
basics
~10 srequireNotNull checks arguments and throws IllegalArgumentException if null. checkNotNull validates internal state and throws IllegalStateException. Both return the non-null value. !! just throws a generic NullPointerException with no message.
solid answer
~40 sAll three turn a possibly-null platform value into a non-null one, but they signal *why* it failed. `requireNotNull(x) { "..." }` is for **preconditions on inputs** — it throws `IllegalArgumentException`, meaning "the caller passed bad data". `checkNotNull(x) { "..." }` is for **invariants / internal state** — it throws `IllegalStateException`, meaning "the object is in an unexpected state". Both are inline, take a lazy message lambda, and **smart-cast** the value to non-null afterward (and return it, so you can write `val v = requireNotNull(x)`). `!!` is the blunt tool: it throws `NullPointerException` with no context and communicates no intent. At a Java boundary you usually know whether a null means "bad argument" or "broken invariant", so prefer the expressive stdlib functions; reserve `!!` for throwaway code or where you have just proven non-nullity.
code
kotlin · 3 linesval id = requireNotNull(dto.id) { "id is required" } // IllegalArgumentException
val session = checkNotNull(currentSession) { "no session" } // IllegalStateException
val token = header() ?: error("missing auth header") // error() -> IllegalStateExceptiongo deeper
Knows all three assert non-null and that !! can crash.
Picks require vs check by intent and exception type and uses the returned value.
Designs boundary guards so failures map to the right exception class for callers and observability.
Standardizes error taxonomy across modules so 'bad input' vs 'broken invariant' is consistent and machine-distinguishable.
## Three ways to assert non-null All three convert a platform/nullable value into a non-null value, failing if it is null — but they differ in **exception type, message, and intent**. ### `requireNotNull(value) { message }` - Throws **`IllegalArgumentException`** when `value` is null. - Semantics: a **precondition on inputs** — "the caller gave me something invalid." - Inline; the message lambda is only built on failure (cheap). - **Returns** the non-null value and smart-casts it. ### `checkNotNull(value) { message }` - Throws **`IllegalStateException`** when `value` is null. - Semantics: an **invariant / state check** — "this object got into an impossible state." - Same inline + smart-cast + return behavior. ### `!!` (not-null assertion operator) - Throws a bare **`NullPointerException`** (actually `KotlinNullPointerException`/`NullPointerException`) with no custom message. - Communicates no intent and gives the reader no "why". ```kotlin fun handle(rawUser: JavaUser /* fields are platform types */) { // input contract: caller must supply an id val id = requireNotNull(rawUser.id) { "user id must not be null" } // internal invariant: by now the cache must be loaded val cache = checkNotNull(loadedCache) { "cache not initialized" } val name = rawUser.name!! // discouraged: no context if it blows up } ``` ## Why type matters at the boundary Framing a failure as `IllegalArgumentException` vs `IllegalStateException` lets callers, logs, and error handlers distinguish **"bad input"** from **"our bug"**. A generic `NullPointerException` from `!!` erases that signal. ## Companion overloads The stdlib also has `require(condition) { ... }` and `check(condition) { ... }` for boolean conditions; the `...NotNull` variants are the null-specific specializations that additionally **return** the value. There is also `error("msg")` which always throws `IllegalStateException` (handy in an Elvis fallback). ## Smart cast After `requireNotNull(x)`/`checkNotNull(x)`, the compiler knows `x` is non-null for the rest of the scope, so you can keep using `x` directly — not only the returned value.
- Do requireNotNull/checkNotNull smart-cast the variable, or only return a value?Both. They return the non-null value and, because they are contract-backed stdlib functions, the original variable is smart-cast to non-null afterward.
- What does error("msg") throw and where is it handy?It throws IllegalStateException and never returns (Nothing), so it fits perfectly as the right side of an Elvis: `x ?: error("...")`.
saying these in an interview costs you the question
- Saying requireNotNull and checkNotNull throw the same exception
- Using !! when a meaningful precondition/invariant is intended
- Claiming the message string is always constructed even on success
- Not knowing they return the value / smart-cast
- Mixing up which one means 'bad argument' vs 'bad state'