Definition
Comparator.nullsLast(wrapped)returns a null-friendly comparator treating null as greater than any non-null value.nullsFirst(wrapped)is the mirror: nulls sort to the top.- Without null-safety:
Comparator.comparing(getCity)throws NullPointerException on a null city. - Usage:
comparing(Employee::getCity, nullsLast(naturalOrder()))safely sorts on a nullable key. - Scope: protects against null field values, not null Employee objects.
Some employees at the National Bank of India have not filled in their city yet, so that field is blank. Priya wants the list sorted by city A to Z, but everyone with a missing city should sit at the bottom rather than crash the sort.
Understanding the Concepts
Why does a null field crash the sort?
A plain Comparator.comparing(Employee::getCity) calls compareTo on the extracted key. If the key is null, compareTo throws a NullPointerException. nullsLast intercepts null values before they reach the wrapped comparator.
How do you sort with nulls at the end?
package com.bank.hr;
import java.util.*;
import java.util.stream.Collectors;
public class NullsLastSort {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Rajesh", "Mumbai"),
new Employee("Priya", null),
new Employee("Amit", "Delhi"),
new Employee("Kavita", null),
new Employee("Suresh", "Chennai"));
List<Employee> sorted = staff.stream()
.sorted(Comparator.comparing(Employee::getCity,
Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Employee::getName))
.collect(Collectors.toList());
sorted.forEach(e -> System.out.printf("%-8s %s%n",
e.getName(), e.getCity() != null ? e.getCity() : "(none)"));
}
}
Suresh Chennai
Amit Delhi
Rajesh Mumbai
Kavita (none)
Priya (none)
How is nullsLast different from nullsFirst?
| Comparator | Null placement | Non-null ordering |
|---|---|---|
| nullsLast(naturalOrder()) | Nulls at the end | Alphabetical |
| nullsFirst(naturalOrder()) | Nulls at the top | Alphabetical |
| nullsLast(reverseOrder()) | Nulls at the end | Reverse alphabetical |
// Nulls first instead of last
List<Employee> nullsTop = staff.stream()
.sorted(Comparator.comparing(Employee::getCity,
Comparator.nullsFirst(Comparator.naturalOrder())))
.collect(Collectors.toList());
What are common mistakes?
Mistake: forgetting to handle nulls and crashing the sort.
// WRONG: NPE when city is null
staff.stream()
.sorted(Comparator.comparing(Employee::getCity)) // NPE!
.collect(Collectors.toList());
// CORRECT: null-safe comparator
staff.stream()
.sorted(Comparator.comparing(Employee::getCity,
Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.toList());
What about null Employee objects?
nullsLast protects against null field values, but not null Employee objects in the stream. Filter those separately.
// Guard against null Employee objects AND null city
staff.stream()
.filter(Objects::nonNull)
.sorted(Comparator.comparing(Employee::getCity,
Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.toList());
What about adding a name tie-breaker within the null group?
Without a tie-breaker, employees in the null-city group appear in an arbitrary order. Adding thenComparing(Employee::getName) makes the null group alphabetical.
What should a senior engineer know?
nullsLastis a decorator: it checks for null before delegating to the wrapped comparator.- It applies to the extracted key value, not the stream element itself.
- For stable, predictable reports, always add a deterministic tie-breaker.
- In a data migration scenario, null fields are common and must be handled explicitly.
What are common interview follow-up questions?
- "What does nullsFirst do?" Sends nulls to the top instead of the bottom.
- "Does nullsLast protect against null Employee objects?" No, only null field values.
- "Can you combine nullsLast with reversed?" Yes:
nullsLast(reverseOrder())sorts non-nulls in reverse.
Visual Overview
Without nullsLast: comparing(getCity) on null → NullPointerException! With nullsLast(naturalOrder()): ┌────────────────────────────────────────────┐ │ Chennai < Delhi < Mumbai < null < null │ │ ↑ alphabetical non-nulls ↑ ↑ nulls end ↑│ └────────────────────────────────────────────┘ With nullsFirst(naturalOrder()): ┌────────────────────────────────────────────┐ │ null < null < Chennai < Delhi < Mumbai │ │ ↑ nulls top ↑ ↑ alphabetical non-nulls ↑│ └────────────────────────────────────────────┘
Key Takeaway
When sorting on a field that can be null I wrap the key comparator with Comparator.nullsLast, which treats null as greater than any real value so employees with a missing city sort to the end instead of crashing with a NullPointerException.
In my banking project at the National Bank of India this came from a half-finished data migration where some employee records had no city. The directory report needed those incomplete records visible but parked at the bottom, and nullsLast did exactly that.
nullsFirst is the mirror image for nulls at the top. nullsLast guards field values, not the Employee object itself, so I filter null objects with Objects::nonNull first. I always add a name tie-breaker so the null group is also ordered predictably.
Remember one sentence: comparing(getCity, nullsLast(naturalOrder())) sorts non-nulls alphabetically and pushes nulls to the end without crashing.
---