Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In Kotlin, a question mark marks a type that may hold null: String? can be null, while String cannot. Use ?. to let absence propagate, ?: to choose a fallback or exit, and an ordinary null check when you need a multi-step branch. !! is an assertion that can throw—not a safe way to handle null.

What does ? mean in Kotlin?

Kotlin makes nullability part of a variable’s type. String and String? are distinct: the first cannot contain null; the second can. The compiler uses that distinction to prevent many accidental null dereferences before the program runs. As the Kotlin null-safety guide puts it, “Kotlin’s null safety ensures safer code by catching potential null-related issues at compile time rather than runtime.”

Because a nullable value may be absent, Kotlin will not let you access its members as if it were definitely present. You must check it, use a safe call, provide an alternative, or explicitly assert that it is non-null.

How do you choose between a null check, ?., ?:, and !!?

Approach Behavior when the value is null Good fit Risk
if (value != null) Takes the null branch; code in the non-null branch can use the value. A branch with several statements or different handling for present and absent values. Low when both cases are handled explicitly.
value?.member or value?.function() Returns null instead of accessing the member or calling the function. When absence should flow onward as a nullable result. Low for the call itself; downstream code still needs to handle a nullable result.
value ?: fallback Evaluates the fallback expression. When a meaningful default is available. Can conceal absence if the fallback is arbitrary or misleading.
value ?: return or value ?: throw ... Exits the function or throws when the value is absent. When a missing value makes further work impossible or invalid. Behavior is explicit, but the chosen exit or exception must match the function’s contract.
value!! Throws NullPointerException. Only when a non-null invariant has already been established and the assertion is intentional. High if null is possible; it converts an unmet assumption into a runtime failure.

Use a check for a multi-step decision

An ordinary check is easiest to read when presence and absence need separate actions:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
if (name != null) {
    println(name.length)
} else {
    println("No name supplied")
}

Use a safe call to propagate absence

A safe call skips the access when the receiver is null and produces a nullable result. For example, name?.length has no length value when name is null. This is useful when the next operation can also accept or preserve null.

Use Elvis when absence needs a result or an exit

The Elvis operator evaluates its right-hand side only if the left-hand expression is null. Its right side can be a value, but it can also be return or throw, since both are expressions in Kotlin:

val displayName = name ?: "Guest"
val requiredName = name ?: return
val validName = name ?: throw IllegalArgumentException("Name is required")

Choose a fallback only when it represents a valid outcome. If missing data means the operation cannot continue, an early return or exception communicates that more accurately.

Treat !! as an assertion, not a conversion

value!! tells Kotlin to treat a nullable value as non-null. If the value is actually null, it throws NullPointerException. Prefer a check, safe call, or explicit Elvis branch when absence is possible. Reserve !! for a boundary or invariant where you can justify that the value cannot be null; Kotlin’s type system does not make the assertion safe.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does Kotlin have Java Optional?

Kotlin’s native way to represent possible absence is a nullable type such as T?, with compiler checks and flow analysis around it. Java’s Optional<T> is a separate Java API type; it can still appear when Kotlin code consumes a Java library that uses it. Handle or adapt it at that API boundary according to the library’s contract. Kotlin’s nullable model does not imply a universal rule that every Java Optional must be removed—or that every Kotlin API should use one.

Why do Java values become platform types in Kotlin?

Java reference types without recognized nullability annotations may reach Kotlin as platform types. Their nullability is unknown, so Kotlin allows more relaxed operations than it would for a declared T?. That flexibility reflects missing information, not a guarantee that the Java value is non-null.

If an actually null platform value is assigned to a non-null Kotlin variable, a NullPointerException can occur. When you know the Java value may be absent, declare it as nullable in Kotlin so the uncertainty is visible and checked at use sites.

Make Java API nullability explicit

Recognized annotations such as JSpecify and JSR-305 can tell Kotlin whether Java declarations are nullable or non-nullable, improving diagnostics at the boundary. Android’s Java-Kotlin interop guidance recommends annotating every non-primitive parameter, return value, and field in a public Java API. Annotations let Kotlin provide stronger compile-time checks than an unannotated platform type.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

What null safety does—and does not—guarantee

For values declared with accurate Kotlin nullability, the type system helps ensure that nullable values are checked before dereferencing. It does not eliminate every possible null-pointer exception: assertions with !!, uncertain Java platform types, inconsistent generic types, explicit throws, and initialization problems remain possible. The practical rule is to make absence explicit at declarations and boundaries, then choose deliberately whether each absent value should propagate, receive a default, or stop the operation.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.