Question

How to Use Nullable Boolean in Kotlin if Expressions

kotlinnull

Question

In Java, if I have a nullable Boolean, I can write code like this:

Boolean b = ...;
if (b != null && b) {
    // Do something
} else {
    // Do something else
}

In Kotlin, I tried to write the equivalent using a nullable Boolean:

val b: Boolean? = ...
if (b != null && b!!) {
    // Do something
} else {
    // Do something else
}

This works, but using !! feels unsafe because it forces a non-null value and seems to bypass Kotlin's null-safety features.

Is there a more idiomatic and elegant way to handle a nullable Boolean in an if expression in Kotlin?

Also, there is an important detail: this works differently for local variables versus properties with backing fields. My Boolean? is actually a property with a backing field, so I would like to understand the correct approach in that case as well.

Short Answer

By the end of this page, you will understand how Kotlin handles nullable Boolean values, why !! is usually unnecessary here, when smart casts work, why properties behave differently from local variables, and the most idiomatic ways to write conditions such as if (b == true) safely.

Concept

In Kotlin, Boolean? means a Boolean that can be:

  • true
  • false
  • null

That is different from plain Boolean, which can only be true or false.

This matters because an if condition must evaluate to a non-null Boolean. Kotlin will not let you directly use a Boolean? where a Boolean is required, because null is a possible value.

A common beginner reaction is to use !!:

if (b != null && b!!) {
    ...
}

While this may work in some cases, !! is not the idiomatic solution. The !! operator means: “I am certain this value is not null, and if I am wrong, throw a NullPointerException.”

Kotlin encourages safer alternatives.

The most common idiomatic pattern is:

if (b == true) {
    ...
}

This works because:

  • if b is true, the condition is true
  • if b is false, the condition is false
  • if b is null, the condition is also false

So b == true is a compact, null-safe way of saying “run this block only when b is explicitly true.”

This concept matters in real programming because nullable values appear everywhere:

  • API responses
  • database fields
  • optional configuration
  • UI state
  • form input
  • model properties

Being able to handle nullable Booleans clearly and safely makes Kotlin code easier to read and less error-prone.

Mental Model

Think of Boolean? as a three-state switch instead of a normal two-state switch.

A normal Boolean is like a light switch with only two positions:

  • ON (true)
  • OFF (false)

A Boolean? adds a third position:

  • ON (true)
  • OFF (false)
  • UNKNOWN (null)

An if statement only knows how to deal with ON or OFF. It cannot directly act on UNKNOWN.

So when you write:

if (b == true)

you are really saying:

  • proceed only if the switch is definitely ON
  • treat OFF and UNKNOWN the same for this condition

That is why this pattern feels natural in Kotlin.

Take Quiz

Syntax and Examples

The most useful ways to handle a nullable Boolean are shown below.

1. Check for explicit true

val b: Boolean? = getFlag()

if (b == true) {
    println("Flag is true")
} else {
    println("Flag is false or null")
}

This is the most idiomatic solution when you only want the true case.

2. Check for explicit false

if (b == false) {
    println("Flag is explicitly false")
}

This excludes null.

3. Handle all three states separately

when (b) {
    true -> println("True")
    false -> println("False")
    null -> println("Null")
}

Use this when null has its own meaning.

4. Smart cast with a local variable

val b: Boolean? = getFlag()

if (b != null && b) {
    println("True")
}

This can work for local variables because Kotlin can prove that b is not null after b != null.

5. Properties may not smart cast

class Example {
    var b: Boolean? = true

    fun test() {
        if (b != null && b == true) {
            println("True")
        }
    }
}

For properties, Kotlin is more careful. A property could change between checks, especially if it is mutable or has a custom getter. In those cases, if (b == true) is cleaner and safer than relying on smart casts.

6. Store the property in a local variable first

class Example {
    var b: Boolean? = true

    fun test() {
        val value = b
        if (value != null && value) {
            println("True")
        }
    }
}

This works because value is now a local snapshot.

Step by Step Execution

Consider this example:

val b: Boolean? = null

if (b == true) {
    println("Do something")
} else {
    println("Do something else")
}

Here is what happens step by step:

  1. b is declared as Boolean?.
  2. Its value is null.
  3. Kotlin evaluates b == true.
  4. Since b is null, the comparison result is false.
  5. The if block is skipped.
  6. The else block runs.

Now with b = false:

val b: Boolean? = false

Execution:

  1. b == true is evaluated.
  2. false == true is false.
  3. The else block runs.

Now with b = true:

val b: Boolean? = true

Execution:

  1. b == true is evaluated.
  2. true == true is true.
  3. The first block runs.

This shows why b == true is so useful: it safely collapses the three possible states into a simple condition for if.

Now compare that with a property:

class Feature {
    var enabled: Boolean? = true

    fun run() {
        val current = enabled
        if (current != null && current) {
            println("Enabled")
        }
    }
}

Step by step:

  1. enabled is a property.
  2. Its value is copied into the local variable current.
  3. Kotlin knows current cannot unexpectedly change during the check.
  4. After current != null, Kotlin smart casts current to Boolean.
  5. The second condition current is valid and safe.

Real World Use Cases

Nullable Booleans show up in many real programs.

API responses

An API might send a field that is missing or unknown:

data class UserSettings(
    val emailVerified: Boolean?
)

if (settings.emailVerified == true) {
    showVerifiedBadge()
}

Database values

A database column may allow nulls:

if (row.isArchived == false) {
    displayRecord()
}

Feature flags

A feature flag might not be initialized yet:

if (featureFlag == true) {
    enableNewCheckout()
}

Form state

A checkbox value may be unset until the user interacts:

if (form.acceptedTerms == true) {
    submitForm()
}

Configuration values

Optional config may default to off when missing:

val debugMode: Boolean? = config["debug"]
if (debugMode == true) {
    println("Debug logging enabled")
}
Take Quiz

Real Codebase Usage

In real Kotlin codebases, developers usually avoid !! unless they have no better option.

Common pattern: explicit true check

if (user.isAdmin == true) {
    showAdminPanel()
}

This is clear and compact.

Guard clause pattern

if (request.isAuthorized != true) return
processRequest()

This is common in service methods and controllers.

Snapshot a property into a local variable

val active = session.isActive
if (active == true) {
    startTracking()
}

Developers do this when smart casting is not available for a property.

Handle three states explicitly with when

when (task.completed) {
    true -> markGreen()
    false -> markRed()
    null -> markGray()
}

This is useful when null has business meaning.

Defaulting with the Elvis operator

val shouldRetry = config.retryEnabled ?: false
if (shouldRetry) {
    retry()
}

This converts a Boolean? into a regular Boolean.

Validation and input handling

val accepted = input.acceptedTerms ?: false
if (!accepted) {
    return error("Terms must be accepted")
}

This pattern is common in backend and Android code.

Common Mistakes

1. Using !! unnecessarily

Broken or risky code:

if (b!!) {
    doSomething()
}

Why it is a problem:

  • crashes if b is null
  • hides the fact that null is possible

Safer version:

if (b == true) {
    doSomething()
}

2. Forgetting that Boolean? is not Boolean

Broken code:

val b: Boolean? = getFlag()
if (b) {
    doSomething()
}

Why it fails:

  • if requires a non-null Boolean
  • b might be null

Fix:

if (b == true) {
    doSomething()
}

3. Expecting smart cast on mutable properties

Broken or confusing code:

class Example {
    var b: Boolean? = true

    fun test() {
        if (b != null && b) {
            println("True")
        }
    }
}

Why it may fail:

  • b is a property
  • Kotlin cannot guarantee it stays unchanged between checks

Fix:

val value = b
if (value != null && value) {
    println("True")
}

Or simply:

if (b == true) {
    println("True")
}

4. Treating null and false as the same when they are not

Sometimes null means “unknown,” not “false.”

Wrong idea:

if (b != true) {
    println("Definitely false")
}

But b != true is true for both false and null.

Use when if you need all three states:

when (b) {
    true -> println("True")
    false -> println("False")
    null -> println("Unknown")
}

5. Overcomplicating simple checks

Too verbose:

if (b != null && b == true) {
    doSomething()
}

Simpler:

if (b == true) {
    doSomething()
}

Comparisons

ApproachWorks with Boolean?Null-safeIdiomaticNotes
if (b)NoNoNob may be null
if (b!!)YesNoUsually noCan throw NullPointerException
if (b != null && b)SometimesYesAcceptableWorks well for local variables
if (b == true)YesYesYesBest choice when only true matters
if ((b ?: false))YesYesSometimesUseful when you want a real Boolean
when (b)YesYesYesBest when true, false, and null all matter

Another useful comparison:

SituationRecommended approach
Local nullable Booleanif (b == true) or if (b != null && b)
Mutable propertyif (b == true) or copy to local variable
Need to distinguish nullwhen (b)
Need a non-null defaultval value = b ?: false
Take Quiz

Cheat Sheet

val b: Boolean? = ...

Common safe checks:

if (b == true) { ... }      // only true
if (b == false) { ... }     // only false
if (b != true) { ... }      // false or null
if (b != false) { ... }     // true or null

Convert to non-null Boolean:

val value = b ?: false
if (value) { ... }

Handle all three states:

when (b) {
    true -> ...
    false -> ...
    null -> ...
}

Smart cast notes:

  • Local val variables often smart cast after b != null
  • Properties may not smart cast
  • Mutable properties are especially restricted
  • Copy a property to a local variable if needed

Avoid when possible:

b!!

Why avoid it:

  • throws if value is null
  • less readable
  • usually unnecessary for Boolean?

Best default rule:

if (b == true)

Use this when you want to execute code only when the nullable Boolean is explicitly true.

FAQ

How do I check a nullable Boolean in Kotlin?

Use:

if (b == true) { ... }

This is the most common idiomatic approach.

Is b!! safe in an if condition?

No. It will crash if b is null. Use b == true unless you have a very specific reason not to.

Why does if (b != null && b) work for local variables but not always for properties?

Kotlin can smart cast local variables more easily because they are stable. Properties may change between checks or use custom getters, so Kotlin is more cautious.

What is the best way to handle a nullable property Boolean?

Usually either:

if (property == true)

or copy it first:

val value = property
if (value != null && value) { ... }

What if null has a different meaning from false?

Use a when expression to handle all three cases explicitly.

Is b ?: false the same as b == true?

They are often used for similar outcomes, but they express slightly different intent. b == true is a direct check for explicit true. b ?: false converts the nullable value into a non-null Boolean first.

When should I use == false?

Use b == false when you need to detect only the explicit false case and exclude null.

Take Quiz

Mini Project

Description

Build a small Kotlin program that evaluates an optional feature flag. This demonstrates how to work with a nullable Boolean safely, how to distinguish between true, false, and null, and how to avoid using !! in everyday code.

Goal

Create a program that prints different messages depending on whether a feature flag is enabled, disabled, or unknown.

Requirements

  • Define a nullable Boolean feature flag
  • Print one message when the flag is true
  • Print a different message when the flag is false
  • Print a third message when the flag is null
  • Avoid using the !! operator
Take Quiz

Keep learning

Related questions

View more