Chapter 7: Working with nullable values
Introduces nullable types as a Kotlin feature that improves code reliability and previews the chapter’s treatment of null values and Java interoperability.
After this chapter you will be able to
- Compile-time null safety. Explain how Kotlin’s type-system support for nullability helps prevent NullPointerExceptions by letting the compiler detect many possible errors before a program runs.
- Nullable and non-nullable types. Distinguish a non-nullable type such as String from a nullable type such as String?, and explain when a function parameter should use each.
- Restrictions on nullable values. Explain why Kotlin rejects direct method calls on nullable values and their use where a non-nullable value is required.
- Null checks and compiler treatment. Describe how comparing a nullable value with null changes what operations the compiler permits in the checked scope.
- Type guarantees and Java null. Explain types as sets of possible values and permitted operations, and why a Java String variable does not reliably tell you which operations are safe without a null check.
- Kotlin nullability and operation safety. Explain how distinguishing nullable from non-nullable types clarifies which operations are allowed, and state whether a nullable type wraps its non-nullable counterpart at run time.
- Safe-call behavior and result. Explain what `?.` does when its receiver is non-null or null, and why the result of a safe call can be nullable even when the called method returns a non-null type.
- Elvis operator fallback. Explain how `?:` chooses between its left and right expressions and how it can provide a default after a safe call.
- Early exits for missing values. Describe how `return` or `throw` on the right side of `?:` handles a null left-hand value, including its use for checking a function precondition.
- Safe casts versus regular casts. Distinguish `as?` from `as` when a value does not have the requested type.
- Safe cast with Elvis. Explain how a safe cast and the Elvis operator can check an `equals` parameter’s type and return false when it is wrong.
- Non-null assertion behavior. Explain how !! converts a nullable value to a non-nullable type and what happens if the value is null.
- Safe-call let for nullable values. Explain how a safe call with let lets you pass a nullable expression’s value to a function requiring a non-nullable argument.
- Deferred non-null property initialization. Explain when to use lateinit instead of making a property nullable, and describe the obligation to initialize it before access.
- Nullable extension receivers. Explain why an extension declared for a nullable type can be called without a safe call, and how it handles a null receiver.
- Nullable receiver let calls. Distinguish calling let directly on a nullable receiver from calling it with the safe-call operator.
- Default type parameter nullability. Explain that a Kotlin type parameter can be replaced by a nullable type, so a value declared as T may be null even though T has no question mark.
- Non-nullable type parameter bounds. Describe how specifying a non-nullable upper bound makes a type parameter non-nullable and rejects a nullable argument.
- Java nullability in Kotlin. Explain how Kotlin treats Java types with and without nullability annotations: annotated types become nullable or non-nullable Kotlin types, while unannotated types become platform types.
- Platform type nullability choices. Explain that a platform type has unknown nullability, so Kotlin permits both direct use and null-safe use, leaving the developer responsible for choosing correctly.
- Java Override Nullability Choices. Explain that a Kotlin override of a Java method may declare its parameters and return type nullable or non-nullable, and describe what happens if Java passes null to a parameter declared non-nullable.
What it builds on
- Declaring functions with parameters and return values (chapter 2) · Declaring functions with typed parameters.
- Smart casts: Combining type checks and casts (chapter 2) · The idea that the compiler can refine how it treats a value after a check.
- Throwing and catching exceptions in Kotlin (chapter 2) · Throwing exceptions in Kotlin.
- Lambdas with receivers: with, apply, and also (chapter 5) · Earlier introductions to scope functions such as with, apply, and also.
- Accessing a backing field from a getter or setter (chapter 4) · The distinction between a Kotlin property and its backing field
- Adding methods to other people’s classes: Extension functions and properties (chapter 3) · Extension functions
Section by section
Introduction
Introduces nullable types as a Kotlin feature that improves code reliability and previews the chapter’s treatment of null values and Java interoperability.
7.1 Avoiding NullPointerExceptions and handling the absence of values: Nullability
The section explains why Kotlin makes nullability part of its type system: to catch many potential null-related errors at compile time rather than as runtime exceptions.
- Compile-time null safety: Explain how Kotlin’s type-system support for nullability helps prevent NullPointerExceptions by letting the compiler detect many possible errors before a program runs.
Why it matters: Finding a possible null error before running the program is less troublesome than having users encounter a runtime failure.
Key terms: nullability, type system, NullPointerException, compile-time errors, run-time errors
7.2 Making possibly null variables explicit with nullable types
This section explains how Kotlin marks types as nullable, restricts their use, and allows a null check to make a value usable as non-null.
- Nullable and non-nullable types: Distinguish a non-nullable type such as String from a nullable type such as String?, and explain when a function parameter should use each.
Why it matters: The type tells callers whether null is an allowed value before the program runs. - Restrictions on nullable values: Explain why Kotlin rejects direct method calls on nullable values and their use where a non-nullable value is required.
Why it matters: These compile-time restrictions catch uses that could otherwise fail with a NullPointerException. - Null checks and compiler treatment: Describe how comparing a nullable value with null changes what operations the compiler permits in the checked scope.
Why it matters: A null check lets you use a possibly null value safely without changing its declared type.
Key terms: nullable type, non-nullable, String?, question mark, non-nullable type, NullPointerException, null comparison, if checks
7.3 Taking a closer look at the meaning of types
Explains why Java’s treatment of null weakens what a declared type guarantees, and how Kotlin’s nullable types address that problem without wrapper objects.
- Type guarantees and Java null: Explain types as sets of possible values and permitted operations, and why a Java String variable does not reliably tell you which operations are safe without a null check.
Why it matters: A declared type is most useful when it tells you which operations are safe. - Kotlin nullability and operation safety: Explain how distinguishing nullable from non-nullable types clarifies which operations are allowed, and state whether a nullable type wraps its non-nullable counterpart at run time.
Why it matters: The type distinction makes possible null-related errors visible without requiring a wrapper for every nullable value. - Limits of Java null alternatives: Describe why Java nullability annotations and Optional do not fully eliminate NullPointerException risks across a codebase and its libraries.
Why it matters: These limitations explain why a language-level nullability distinction is useful even when Java projects already use null-safety conventions.
Key terms: type, String, null, NullPointerException, nullable types, non-nullable types, intrinsic checks, @Nullable, @NotNull, Optional
7.4 Combining null checks and method calls with the safe call operator: ?.
This section explains how Kotlin’s safe-call operator combines a null check with a method or property access, including across several nullable properties.
- Safe-call behavior and result: Explain what `?.` does when its receiver is non-null or null, and why the result of a safe call can be nullable even when the called method returns a non-null type.
Why it matters: It lets you read a safe call as both a null check and an operation, without overlooking the nullable result. - Safe calls across properties: Describe how safe calls can access nullable properties and be chained to traverse an object graph where more than one property may be absent.
Why it matters: Chaining safe calls keeps access through nullable relationships concise.
Key terms: safe-call operator, nullable, ?., safe calls, nullable properties, object graph
7.5 Providing default values in null cases with the Elvis operator: ?:
This section explains how the Elvis operator supplies a fallback for null values or exits a function when a required value is missing.
- Elvis operator fallback: Explain how `?:` chooses between its left and right expressions and how it can provide a default after a safe call.
Why it matters: It lets you provide a useful default when an expression may produce null. - Early exits for missing values: Describe how `return` or `throw` on the right side of `?:` handles a null left-hand value, including its use for checking a function precondition.
Why it matters: It provides a concise way to stop a function with a deliberate result or meaningful error when a required value is absent.
Key terms: Elvis operator, null-coalescing operator, safe-call operator, return, throw, preconditions
7.6 Safely casting values without throwing exceptions: as?
This section explains how `as?` avoids cast exceptions and how to combine it with the Elvis operator to reject values of the wrong type.
- Safe casts versus regular casts: Distinguish `as?` from `as` when a value does not have the requested type.
Why it matters: This distinction lets you choose whether a failed cast should throw or produce a nullable result. - Safe cast with Elvis: Explain how a safe cast and the Elvis operator can check an `equals` parameter’s type and return false when it is wrong.
Why it matters: The pattern makes a type check, cast, and wrong-type return concise while preserving type-safe use of the result.
Key terms: as, as?, ClassCastException, safe cast, Elvis operator, smart casts, equals
7.7 Making promises to the compiler with the non-null assertion operator: !!
Explains what Kotlin’s non-null assertion does, when it can be justified, and why it should be used cautiously.
- Non-null assertion behavior: Explain how !! converts a nullable value to a non-nullable type and what happens if the value is null.
Why it matters: It lets you recognize both the convenience and the run-time risk of !!. - Cross-function assertion use: Justify using !! when a null check in one function guarantees a value used in another function, and describe the early-return alternative shown here.
Why it matters: It helps you decide whether to rely on an external guarantee or guard against that guarantee changing. - Assertion failure diagnostics: Describe why putting multiple !! assertions on one line makes a null failure harder to diagnose.
Why it matters: Separating assertions can make a failure easier to locate.
Key terms: non-null assertion, !!, non-nullable type, Elvis operator, isActionEnabled, executeCopyRow, stack trace
7.8 Dealing with nullable expressions: The let function
This section shows how safe-call let handles a nullable expression and compares let with other Kotlin scope functions.
- Safe-call let for nullable values: Explain how a safe call with let lets you pass a nullable expression’s value to a function requiring a non-nullable argument.
Why it matters: This pattern avoids a separate null check when using a value that must be non-null. - Choosing concise null checks: Describe when safe-call let is convenient for a longer nullable expression and why a regular if expression is often preferable for checking multiple values.
Why it matters: Choosing the concise form should not make a multi-value null check harder to read. - Scope function use cases: Distinguish the uses this section gives for let, apply, also, with, and run.
Why it matters: Their overlapping syntax makes the intended task a useful guide when choosing a scope function.
Key terms: let, safe-call operator, nullable type, non-nullable parameter, it, nested let calls, if expression, apply, also, with
7.9 Non-null types without immediate initialization: Late-initialized properties
This section explains how lateinit permits deferred initialization of non-null properties and why it is useful with frameworks that initialize objects after construction.
- Deferred non-null property initialization: Explain when to use lateinit instead of making a property nullable, and describe the obligation to initialize it before access.
Why it matters: It lets you express that a property should be non-null once setup is complete, even when its value is not available in the constructor. - Framework-assigned lateinit properties: Describe how lateinit properties support frameworks that assign values after object creation, including the generated field visibility.
Why it matters: The generated field lets Java frameworks supply a value during their own initialization process.
Key terms: lateinit, non-nullable type, var, !! operator, lateinit properties, Java dependency injection frameworks, field, visibility
7.10 Extending types without the safe-call operator: Extensions for nullable types
This section explains how extensions on nullable types handle null receivers, why calling let without a safe call does not filter out null, and when to define a nullable-receiver extension.
- Nullable extension receivers: Explain why an extension declared for a nullable type can be called without a safe call, and how it handles a null receiver.
Why it matters: A call such as s.isNullOrBlank() does not prove that s is non-null. - Nullable receiver let calls: Distinguish calling let directly on a nullable receiver from calling it with the safe-call operator.
Why it matters: A direct let call does not guard a block that requires a non-null value. - Nullable extension design choice: Describe the section’s default choice for defining an extension and when changing it to accept a nullable receiver makes sense.
Why it matters: Choosing a nullable receiver should reflect whether the function has a sensible behavior for null.
Key terms: extension functions for nullable types, nullable receiver, this, isNullOrBlank, let, safe-call operator, extension function, nullable type, non-nullable type
7.11 Nullability of type parameters
Explains why a generic type parameter can allow null without a question mark and how a non-nullable upper bound prevents that.
- Default type parameter nullability: Explain that a Kotlin type parameter can be replaced by a nullable type, so a value declared as T may be null even though T has no question mark.
Why it matters: It explains why the absence of a question mark after T does not guarantee a non-null value. - Non-nullable type parameter bounds: Describe how specifying a non-nullable upper bound makes a type parameter non-nullable and rejects a nullable argument.
Why it matters: It identifies the constraint needed when generic code must not accept null.
Key terms: type parameter, nullable type, Any?, non-nullable upper bound
7.12 Nullability and Java
Explains how Kotlin interprets Java types when nullability annotations are present or absent.
- Java nullability in Kotlin: Explain how Kotlin treats Java types with and without nullability annotations: annotated types become nullable or non-nullable Kotlin types, while unannotated types become platform types.
Why it matters: This distinction tells you what nullability information Kotlin can obtain from a Java declaration.
Key terms: @Nullable, @NotNull, String?, platform type
7.12.1 Platform types
Explains how Kotlin handles Java types with unknown nullability, why platform types exist, and what risks developers must manage when using them.
- Platform type nullability choices: Explain that a platform type has unknown nullability, so Kotlin permits both direct use and null-safe use, leaving the developer responsible for choosing correctly.
Why it matters: Kotlin cannot protect you from a wrong nullability assumption when a Java API supplies no nullability information. - Reason for platform types: Justify why Kotlin does not treat every value from Java as nullable, using the unnecessary checks this would impose on values that cannot be null.
Why it matters: The design trades automatic caution for practical use of Java APIs whose nullability is unspecified. - Platform type notation and assignments: Describe where platform types come from, what the displayed String! notation means, and why assigning a Java value to a non-nullable Kotlin variable requires care.
Why it matters: Recognizing the notation helps you interpret compiler and IDE output without mistaking it for syntax you should write.
Key terms: platform type, nullable, non-nullable, NullPointerException, platform types, null checks, ArrayList<String?>?, String!, non-nullable Kotlin variable
7.12.2 Inheritance
This section explains how Kotlin implementations of Java methods choose nullability and why non-nullable parameters can cause runtime exceptions when Java passes null.
- Java Override Nullability Choices: Explain that a Kotlin override of a Java method may declare its parameters and return type nullable or non-nullable, and describe what happens if Java passes null to a parameter declared non-nullable.
Why it matters: The nullability you choose for a Java method override affects what happens when Java code calls it.
Key terms: overriding, nullable, non-nullable, non-null assertions
7.13 Summary
Where this is used later
- (chapter intro) (chapter 8) · Introduces nullability as an existing reliability feature of Kotlin’s type system.
- Understanding the differences between classes, types, and subtypes (chapter 11) · A variable’s type specifies its possible values.
- Parameters with function types can provide defaults or be nullable (chapter 10) · The safe-call operator
- The Nothing type: “This function never returns” (chapter 8) · The Elvis operator’s right-hand side handles a null value.
- Excluding nullable type arguments by explicitly marking type parameters as non-null (chapter 11) · Provides the nullability rule behind restricting generic type arguments.
- Collections of nullable values and nullable collections (chapter 8) · Nullability of type arguments
- Collections declared in Java are seen as platform types in Kotlin (chapter 8) · Applies the same platform-type uncertainty to collections declared in Java.
- Any and Any?: The root of the Kotlin type hierarchy (chapter 8) · Java types whose nullability is unknown in Kotlin.
Concepts explained in this chapter
- non-nullable typeA non-nullable type is a Kotlin type whose values are not permitted to be null by the type system.
- nullable typeA nullable type is a type whose values may include null and whose unsafe operations are restricted by the type system.
- safe-call operatorThe safe-call operator `?.` conditionally invokes a method or accesses a property only when its receiver is non-null, otherwise producing null.
- letlet is a Kotlin standard-library function that executes a lambda with the receiver supplied as an argument and returns the lambda's result.
- type systemA type system is the set of rules a programming language uses to classify values and expressions and check their compatibility.
- !! operatorThe non-null assertion operator `!!` asserts that a nullable expression is non-null and throws an exception if that assertion is false.
- platform typeA platform type is a Kotlin type imported from Java when the Java declaration provides no nullability information, leaving Kotlin unable to determine whether its value is nullable.
- platform typeA platform type is a Kotlin type imported from Java when the Java declaration provides no nullability information, leaving Kotlin unable to determine whether its value is nullable.
- atomic typeA type is a classification that specifies the values an expression or variable can represent and the operations allowed on them.
- missing valueA null value represents the absence of an object or reference value in a program.
- smart castA smart cast is Kotlin's compiler-supported automatic treatment of a value as a checked subtype within a valid control-flow branch.
- nullish coalescing operatorThe Elvis operator `?:` evaluates its left operand when non-null and otherwise evaluates and returns its right operand.
- extensionAn extension function is a Kotlin function defined outside a class that can be called with member-like syntax on values of an existing receiver type.
- NullPointerExceptionNullPointerException is a runtime exception raised when code dereferences a null reference where a non-null object is required.
- upper boundAn upper bound is a constraint specifying a supertype that every valid substitution for a type parameter must satisfy.
- genericsA generic function is a function declared with one or more type parameters so it can operate on values of different types.
- interopJava interoperability is the ability of Kotlin code to interact directly with Java code and libraries, including exchanging compatible string values without conversion.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
