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:
truefalsenull
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
bistrue, the condition istrue - if
bisfalse, the condition isfalse - if
bisnull, the condition is alsofalse
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.
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:
bis declared asBoolean?.- Its value is
null. - Kotlin evaluates
b == true. - Since
bisnull, the comparison result isfalse. - The
ifblock is skipped. - The
elseblock runs.
Now with b = false:
val b: Boolean? = false
Execution:
b == trueis evaluated.false == trueisfalse.- The
elseblock runs.
Now with b = true:
val b: Boolean? = true
Execution:
b == trueis evaluated.true == trueistrue.- 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:
enabledis a property.- Its value is copied into the local variable
current. - Kotlin knows
currentcannot unexpectedly change during the check. - After
current != null, Kotlin smart castscurrenttoBoolean. - The second condition
currentis 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")
}
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
bisnull - 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:
ifrequires a non-nullBooleanbmight benull
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:
bis 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
| Approach | Works with Boolean? | Null-safe | Idiomatic | Notes |
|---|---|---|---|---|
if (b) | No | No | No | b may be null |
if (b!!) | Yes | No | Usually no | Can throw NullPointerException |
if (b != null && b) | Sometimes | Yes | Acceptable | Works well for local variables |
if (b == true) | Yes | Yes | Yes | Best choice when only true matters |
if ((b ?: false)) | Yes | Yes | Sometimes | Useful when you want a real Boolean |
when (b) | Yes | Yes | Yes | Best when true, false, and null all matter |
Another useful comparison:
| Situation | Recommended approach |
|---|---|
| Local nullable Boolean | if (b == true) or if (b != null && b) |
| Mutable property | if (b == true) or copy to local variable |
Need to distinguish null | when (b) |
| Need a non-null default | val value = b ?: false |
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
valvariables often smart cast afterb != 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.
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
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.