Hello! Welcome to your second module, where we'll explore some of Kotlin's most powerful features for writing robust and concise code.
In the first module, we covered the fundamentals of Kotlin syntax, including variables, strings, and control flow. Now, we'll build on that foundation to tackle a concept that directly addresses one of the most common sources of bugs in languages like Java: the NullPointerException.
This lesson focuses on Kotlin's elegant solution to this problem: its null safety system. We will explore the core concept of nullability and learn how Kotlin's type system differentiates between types that can hold null and those that cannot. For someone with your background in Java, this feature will likely be a significant and welcome change.
Learning Outcome: Describe the concept of nullability and differentiate between nullable and non-nullable types.
1. The Problem with null: A Quick Recap
As a Java developer, you're undoubtedly familiar with the NullPointerException (NPE). It occurs at runtime when you try to access a member (a method or property) of a variable that holds a null reference. This issue is so pervasive that its inventor, Tony Hoare, famously called it his "billion-dollar mistake."
Kotlin was designed from the ground up to solve this problem. Instead of leaving null checks to the developer at runtime, Kotlin builds null safety directly into its type system, catching potential NPEs at compile time.
To understand the motivation behind this, let's start with the official Kotlin documentation.
This first reading from the official Kotlin documentation introduces the concept of null safety and explains why it's a cornerstone of the language.
Please read the introductory section titled 'Null safety'. Focus on understanding Kotlin's core philosophy for preventing NullPointerException.
2. Non-Nullable Types: The Default in Kotlin
The most fundamental rule of Kotlin's null safety system is simple: by default, types are non-nullable.
This means that unless you specify otherwise, a variable cannot hold a null value. This is a complete reversal from Java, where any object reference can be null.
Let's see what this looks like. In the previous module, you learned to declare variables like this:
var language: String = "Kotlin" // Explicitly typed
val version = 1.9 // Inferred as Int
In both cases, the compiler enforces that these variables can never be assigned a null value. Attempting to do so will result in a compile-time error, not a runtime crash.
var language: String = "Kotlin"
language = null // COMPILE ERROR! Null can not be a value of a non-null type String
This compile-time check is the heart of Kotlin's null safety. It guarantees that if you have a variable of type String, it holds an actual String instance, and you can safely call its methods (e.g., language.length) without an NPE.
The following video provides a clear demonstration of this default behavior, contrasting it directly with the common experience in Java.
Kotlin Null Safety Explained - How to Avoid Null Pointer Exceptions: Kotlin Fundamentals - Part 5
This video from Codetutor provides a practical look at how Kotlin prevents null assignments by default.
Watch the video from the beginning until 01:31. Pay close attention to the compile-time error that occurs when trying to assign null to a standard variable.
3. Nullable Types: Opting in to null
Of course, there are legitimate scenarios where you need a variable to represent the absence of a value. For this, Kotlin provides nullable types.
To declare a variable as nullable, you append a question mark (?) to its type.
var name: String? = "Alex"
name = null // This is now perfectly valid.
By adding the ?, you are explicitly telling the compiler, "I am aware that this variable might be null, and I will handle it." The type String? is distinct from String. It represents a value that is either a String or null.
Now, the compiler's role reverses. If you have a nullable variable, the compiler will prevent you from accessing its properties or methods directly, because doing so would risk an NPE.
var name: String? = "Alex"
// val length = name.length // COMPILE ERROR!
// Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
This error forces you to perform a null check before using the variable. We will cover the idiomatic ways to perform these checks in the next lesson. For now, the key takeaway is that the ? syntax creates a nullable type, which the compiler treats with caution.
The following resources provide a detailed explanation and more examples.
Nullable types and non-nullable types
This section of the Kotlin documentation formally defines nullable and non-nullable types and shows the clear distinction in their declaration and usage.
Read the section 'Nullable types and non-nullable types'. Focus on the code examples that demonstrate declaring a nullable variable with ? and the resulting compiler error when you try to access a property without a null check.
To further solidify this from your Java perspective, the next reading explains how Kotlin's compile-time checks for nullability work without adding runtime overhead, a point that might be of particular interest given your backend experience.
Nullability in Java and Kotlin
This guide, aimed at Java developers, explains the technical implementation of Kotlin's nullable types and how they differ from Java's approach.
Read the section 'Support for nullable types'. It highlights that nullability checks are performed at compile time, which means there is almost no performance penalty at runtime.
4. Code Exercise: See it in Action
Let's apply what you've learned. Open your IntelliJ IDEA project from the first module.
-
Create a new Kotlin file named
Nullability.kt. -
Inside the
mainfunction, add the following code snippets and observe the compiler's behavior for each one.fun main() { // 1. Declare a non-nullable String. // Try to assign null to it. What happens? var nonNullableName: String = "Kotlin" // nonNullableName = null // Uncomment this line to see the error. println("Non-nullable value: $nonNullableName") // 2. Declare a nullable String and assign null to it. // This should compile without any issues. var nullableName: String? = "Java" nullableName = null println("Nullable value: $nullableName") // 3. Attempt to access a property on a nullable type. // What does the compiler say? nullableName = "Spring" // val length = nullableName.length // Uncomment this line. // The compiler error hints at the solution, which we'll cover next! }
This simple exercise should make the distinction between String and String? tangible. You've now seen firsthand how Kotlin's type system guides you toward writing safer code.
Conclusion
In this lesson, we've explored the fundamental concept of nullability in Kotlin. You now understand how it directly addresses the NullPointerException problem that is common in Java.
Key Takeaways:
- Kotlin's type system is null-safe, designed to eliminate
NullPointerExceptions at compile time. - By default, all types in Kotlin are non-nullable and cannot hold a
nullvalue. - To allow a variable to hold
null, you must explicitly declare it as a nullable type by adding a?to the type name (e.g.,String?). - The compiler enforces these rules: it prevents
nullassignment to non-nullable types and restricts direct access to members of nullable types.
Next Lesson Preview:
Now that you can differentiate between nullable and non-nullable types, the next logical question is: "How do I safely work with a nullable variable?" In the next lesson, we will answer exactly that by learning about Kotlin's powerful and concise null-handling operators, starting with the safe call operator (?.).