Difficulty Level: đđ Beginner to Intermediate
What Youâll Learn
â
Why the !!
operator is dangerous and causes app crashes
â
4 safe alternatives to the not-null assertion operator
â
Real-world examples of proper null safety patterns
â
Best practices for bulletproof Android development
â
How to migrate existing !!
usage to safer alternatives
Quick Answer: Replace
!!
with safe calls (?.
), Elvis operator (?:
), explicit null checks, orlet
functions to prevent crashes and write more robust Kotlin code.
Hey there, Kotlin explorer! đ
Let me guess: youâve discovered Kotlinâs null safety system, and youâre probably thinking âWhy is the compiler complaining so much about nulls?â And then you found this magical little operator !!
that makes all those red squiggles disappear like magic. Am I right?
I totally get it. I remember when I first made the switch from Java to KotlinâI was so frustrated with all the null safety ânaggingâ that I started sprinkling !!
operators everywhere like fairy dust. Problem solved, right? Wrong. So very, very wrong.
Hereâs the honest truth: Iâve seen more production crashes caused by !!
than I care to admit. Including some embarrassing ones in my own early Kotlin projects. But you know what? Thatâs exactly how we learn!
đ A Gentle Reminder: If youâre currently using
!!
everywhere, donât feel bad about it. Seriously. Every Kotlin developer has been there. The fact that youâre reading this post means youâre ready to level up your null safety game, and thatâs awesome!
Whether youâre coming from Java (like I did), transitioning from Unity C# development to native Android, or youâre completely new to programming, this post will help you understand why !!
can be dangerous andâmore importantlyâshow you the elegant, safe alternatives that will make your code more robust and your future self very grateful. These principles are especially crucial when building modern Jetpack Compose applications where null state can break your UI.
The Kotlin Null Safety Problem with !! (Or: How I Learned to Stop Worrying and Love Null Safety)
đŻ Personal Confession: My first Android app crashed within 5 minutes of being published because I had used
!!
on an API response. The API decided to return null during a server outage. Oops! đ
When youâre learning Kotlin, the compilerâs null safety can feel like having an overly protective parent: âDid you check if thatâs null? What about this? Are you sure that canât be null?â Itâs tempting to just shout âI KNOW WHAT IâM DOING!â and silence everything with `!!â:
// â Bad: Using !! everywhere to silence compiler warnings
class UserProfileActivity : AppCompatActivity() {
private lateinit var nameEditText: EditText
private lateinit var emailEditText: EditText
private fun saveUserProfile() {
val name = nameEditText.text.toString()
val email = emailEditText.text.toString()
// Dangerous: What if the API returns null?
val user = getUserFromApi()!!
user.updateProfile(name, email)
// Dangerous: What if the database query fails?
val preferences = getSharedPreferences("user_prefs", MODE_PRIVATE)!!
preferences.edit()!!.putString("last_saved", getCurrentTimestamp()!!)!!.apply()
// Dangerous: What if the intent has no extras?
val userId = intent.extras!!.getString("user_id")!!
processUser(userId)
}
}
Now, hereâs the scary part: this code compiles perfectly and runs beautifully⌠until it doesnât. Every single !!
in there is essentially you making a promise to the Kotlin runtime: âI absolutely, 100% guarantee this will never be null.â
But hereâs the thing about guarantees in programming: the universe has a wicked sense of humor.
đĽ Reality Check: Each
!!
is like a little ticking time bomb in your code. It might not explode today, it might not explode tomorrow, but when it does explode (and it will), itâll crash your app faster than you can say âKotlinNullPointerException.â
Why !! Is Dangerous (And Why Your Future Self Will Thank You for Reading This)
đ¤ Think About It: Kotlinâs null safety isnât trying to annoy youâitâs trying to save you from the pain that millions of Java developers have experienced with NullPointerExceptions. Donât fight it, embrace it!
1. Youâre Trading Compile-Time Safety for Runtime Russian Roulette
Kotlinâs null safety is like having a really good friend who stops you from making bad decisions. Using !!
is like telling that friend to shut up and let you do whatever you want. Sure, you feel free in the moment, butâŚ
2. The Error Messages Are About as Helpful as a Chocolate Teapot
When !!
crashes your app, the error message is basically: âSomething was null, good luck figuring out what!â Itâs like getting a text that says âIâm angry at youâ without any context. Not helpful.
3. Itâs False Confidence in Code Form
!!
looks bold and confident, like you know exactly what youâre doing. In reality, itâs the programming equivalent of saying âHold my coffee and watch this!â before doing something potentially dangerous.
4. Production Debugging Nightmares
Trying to debug a !!
crash in production is like trying to find a needle in a haystack while blindfolded. The crash might only happen when Mars is in retrograde and someone in Denmark sneezes.
đĄ Lightbulb Moment: Every time you write
!!
, ask yourself: âWhat happens if this IS null?â If your answer is âIt canât be!â, thatâs exactly when it will be.
Safe Kotlin Null Safety Alternatives to !! (The Good Stuff That Actually Works!)
đ Mindset Shift: Instead of fighting Kotlinâs null safety, letâs learn to dance with it. These alternatives arenât just saferâtheyâre actually more expressive and cleaner once you get the hang of them!
Alternative 1: Safe Call Operator (?.) - Your New Best Friend!
đ Fun Fact: I call the safe call operator the âmaybe operatorâ because it basically says âmaybe do this, but only if itâs safe.â Itâs like having a responsible friend who doesnât let you text your ex at 2 AM.
The safe call operator ?.
executes the operation only if the value is not null. Itâs polite, itâs safe, and it wonât crash your app:
// â
Good: Using safe calls
class UserProfileActivity : AppCompatActivity() {
private lateinit var nameEditText: EditText
private lateinit var emailEditText: EditText
private fun saveUserProfile() {
val name = nameEditText.text.toString()
val email = emailEditText.text.toString()
// Safe: Only calls updateProfile if user is not null
getUserFromApi()?.updateProfile(name, email)
// Safe: Only saves preferences if available
getSharedPreferences("user_prefs", MODE_PRIVATE)
?.edit()
?.putString("last_saved", getCurrentTimestamp())
?.apply()
}
}
Why This is Amazing:
- â No crashes, ever - If something is null, it just gracefully does nothing
- â Super readable - Anyone can understand whatâs happening
- â Kotlin-y - Youâre working with the language, not against it
- â Chainable - You can chain multiple safe calls together like a safety conga line
đŤ Pro Tip: Safe calls are like wearing a seatbeltâyou might not need it most of the time, but when you do need it, youâre really glad itâs there!
Alternative 2: Elvis Operator (?:) for Default Values
Use the Elvis operator to provide fallback values:
// â
Good: Providing sensible defaults
class UserRepository {
private fun processUserData(userData: UserData?) {
// Provide default values instead of crashing
val userName = userData?.name ?: "Anonymous User"
val userAge = userData?.age ?: 0
val isVerified = userData?.isEmailVerified ?: false
// Safe to use these values
updateUserInterface(userName, userAge, isVerified)
}
private fun loadUserPreferences(): UserPreferences {
val savedPrefs = getSharedPreferences("user_prefs", MODE_PRIVATE)
?.getString("preferences", null)
// Return default preferences if saved ones don't exist
return if (savedPrefs != null) {
parseUserPreferences(savedPrefs)
} else {
UserPreferences.createDefault()
}
}
}
Alternative 3: Explicit Null Checks with Early Returns
Sometimes you need to handle null cases explicitly:
// â
Good: Explicit null handling with early returns
class OrderProcessor {
private fun processOrder(orderId: String?) {
// Handle null case explicitly
if (orderId.isNullOrEmpty()) {
showError("Invalid order ID")
return
}
val order = orderRepository.findById(orderId)
if (order == null) {
showError("Order not found")
return
}
// Safe to process order here
calculateTotal(order)
sendConfirmationEmail(order.customerEmail)
}
private fun updateUserStatus(user: User?) {
user ?: run {
Log.w("UserManager", "Attempted to update null user")
return
}
// User is guaranteed to be non-null here
user.lastActiveTime = System.currentTimeMillis()
userRepository.save(user)
}
}
Alternative 4: let Function for Safe Operations
Use the let
function to perform operations only on non-null values:
// â
Good: Using let for safe operations
class ImageLoader {
private fun loadUserAvatar(user: User?) {
// Only execute the block if user is not null
user?.profileImageUrl?.let { imageUrl ->
if (imageUrl.isNotEmpty()) {
loadImageIntoView(imageUrl, avatarImageView)
}
}
}
private fun processApiResponse(response: ApiResponse?) {
response?.data?.let { data ->
when (data.status) {
"success" -> handleSuccess(data.result)
"error" -> handleError(data.errorMessage)
else -> handleUnknownStatus()
}
} ?: run {
// Handle the case where response or data is null
handleNetworkError()
}
}
}
When !! Might Be Acceptable
There are very rare cases where !!
is acceptable, but they should be heavily documented:
// â ď¸ Acceptable: When you have a guarantee from the platform
class MainActivity : AppCompatActivity() {
private fun handleIntent() {
// Platform guarantee: Activities always have an intent
val action = intent.action
// Still prefer safe alternatives when possible
val extras = intent.extras
if (extras != null) {
val userId = extras.getString("user_id")
// Handle userId safely
}
}
}
// â ď¸ Acceptable: After explicit null check (but prefer smart casting)
private fun processUser(user: User?) {
if (user != null) {
// Kotlin smart cast makes !! unnecessary here anyway
user.updateLastSeen() // No !! needed, user is smart cast to non-null
}
}
Safe Null Handling Patterns
Pattern 1: Builder Pattern with Validation
// â
Good: Safe builder pattern
class UserRequestBuilder {
private var name: String? = null
private var email: String? = null
private var age: Int? = null
fun setName(name: String?): UserRequestBuilder {
this.name = name?.trim()?.takeIf { it.isNotEmpty() }
return this
}
fun setEmail(email: String?): UserRequestBuilder {
this.email = email?.lowercase()?.takeIf { it.contains("@") }
return this
}
fun build(): UserRequest? {
val validName = name ?: return null
val validEmail = email ?: return null
return UserRequest(
name = validName,
email = validEmail,
age = age ?: 0
)
}
}
Pattern 2: Extension Functions for Safe Operations
// â
Good: Extension functions for common null-safe operations
fun String?.isValidEmail(): Boolean {
return this?.contains("@") == true && this.contains(".")
}
fun List<Any>?.isNotNullOrEmpty(): Boolean {
return this != null && this.isNotEmpty()
}
fun <T> T?.orDefault(default: T): T {
return this ?: default
}
// Usage
private fun validateUserInput() {
val emailInput = emailEditText.text.toString()
if (!emailInput.isValidEmail()) {
showError("Please enter a valid email")
return
}
val userTags = getUserTags().orDefault(emptyList())
if (userTags.isNotNullOrEmpty()) {
processUserTags(userTags)
}
}
Migration Strategy: Fixing Existing !! Usage
If you have code with !!
that you need to fix:
Step 1: Identify the Risk Level
// High risk: External data sources
val userData = apiClient.getUser()!! // â High crash risk
// Medium risk: Internal nullability
val preferences = getSharedPreferences("app", MODE_PRIVATE)!! // â ď¸ Medium risk
// Low risk: Platform guarantees (but still avoid)
val context = getApplicationContext()!! // â ď¸ Usually safe but unnecessary
Step 2: Replace with Safe Alternatives
// â
Fixed: Safe handling of external data
val userData = apiClient.getUser()
if (userData != null) {
processUser(userData)
} else {
handleUserNotFound()
}
// â
Fixed: Safe preferences access
getSharedPreferences("app", MODE_PRIVATE)
?.getString("user_id", null)
?.let { userId ->
loadUserProfile(userId)
}
Frequently Asked Questions
Q: When is it actually safe to use !!
?
A: Only when you have a platform guarantee (like intent
in an Activity) or after an explicit null check where Kotlinâs smart casting would work anyway. Always document why itâs safe.
Q: Whatâs the performance difference between ?.
and !!
?
A: Negligible. The safe call operator adds minimal overhead compared to the cost of handling crashes and debugging null pointer exceptions.
Q: Should I use ?.
or explicit null checks?
A: Use ?.
for simple operations and explicit checks when you need different behavior for null vs non-null cases. Explicit checks are better for complex conditional logic.
Q: How do I convert existing !!
code safely?
A: 1) Identify the risk level, 2) Replace with appropriate safe alternatives, 3) Add unit tests with null inputs, 4) Test thoroughly in different scenarios.
Q: Does null safety affect app performance?
A: Null safety improves performance by preventing crashes and reducing debugging time. The runtime overhead is minimal compared to the benefits.
Best Practices Summary
- Treat !! as a code smell: Every
!!
should be justified with a comment explaining why itâs safe - Use safe calls (
?.
) by default: Let the operation gracefully handle null cases - Provide defaults with Elvis (
?:
): Give meaningful fallback values - Handle null cases explicitly: Use early returns or explicit null checks
- Leverage
let
,run
,also
: Use scope functions for safe operations - Create extension functions: Build reusable null-safe utilities
- Test null scenarios: Write unit tests that pass null values to your functions
The Bottom Line (And a Gentle Pep Talk) đ
đ Heart to Heart: You know what makes a great developer? Not the one who never makes mistakes, but the one who learns from them and gets better. Right now, by reading this, youâre becoming that developer.
Kotlinâs null safety isnât your enemyâitâs your friend whoâs looking out for you. Yes, it might seem annoying at first (like a friend who wonât let you leave the house without a jacket), but itâs trying to save you from pain you donât even know youâre about to experience.
Hereâs what I want you to remember:
You donât have to be perfect. You donât have to rewrite all your code overnight. Just start making better choices, one null check at a time. Replace one !!
with a safe call. Add one Elvis operator. Write one explicit null check.
Every safe null handling pattern you learn makes you a better developer. Every crash you prevent makes your users happier. Every time you choose safety over shortcuts, youâre building better software. These principles become especially important when building Compose applications where null state can break your UI.
đ Youâve Got This: The fact that you care enough about code quality to read a whole blog post about null safety tells me everything I need to know about the kind of developer you are (or are becoming). Keep that curiosity, keep learning, and keep building awesome things!
Remember: if you find yourself reaching for !!
, just pause for a second and ask âIs there a safer way?â Nine times out of ten, there is. And that one time out of ten? Document the heck out of it so future you knows why it was necessary. This careful approach extends to other Kotlin fundamentals like mastering @Composable annotations and proper Compose state management patterns.
Your users, your teammates, and your future self will thank you. Trust me on this one. đ
Need help building robust Android applications? Contact Angry Shark Studio for expert Kotlin and Android development services, or explore our mobile development portfolio to see how weâve built crash-free apps for clients worldwide.
Related Reading:

About Angry Shark Studio
Angry Shark Studio is a professional Unity AR/VR development studio specializing in mobile multiplatform applications and AI solutions. Our team includes Unity Certified Expert Programmers with extensive experience in AR/VR development.
Related Articles
More Articles
Explore more insights on Unity AR/VR development, mobile apps, and emerging technologies.
View All Articles