How do you sort a collection that may contain null elements (or null sort keys) safely with a Comparator?
answer
- plain comparators NPE on null -> wrap with nullsFirst/nullsLast
- null vs null = equal; null vs non-null = first/last per wrapper
- null elements: wrap the OUTER comparator
- null keys: wrap the KEY comparator inside comparing(extractor, nullsLast(...))
- wrapped comparator may be null (only null-handling)
basics
~10 sMost comparators throw NullPointerException on null. Wrap your comparator with Comparator.nullsFirst(...) or nullsLast(...) so nulls are placed at the start or end instead of crashing.
solid answer
~40 sA plain comparator typically calls methods on the elements (or their keys), so a null throws NullPointerException during the sort. Comparator.nullsFirst(cmp) and Comparator.nullsLast(cmp) return null-tolerant wrappers: they treat null as comparing before (nullsFirst) or after (nullsLast) every non-null, two nulls as equal, and delegate to the wrapped comparator only when both arguments are non-null. There are two distinct cases. For null *elements*, wrap the whole comparator: nullsLast(naturalOrder()). For null *keys* inside non-null elements, put the wrapper inside comparing's key-comparator slot: comparing(Person::getCity, nullsLast(naturalOrder())). You can also combine with thenComparing, and the underlying object sort stays stable. The wrapped comparator may be null when nulls only ever compare against nulls or you only need null-vs-nonnull handling.
go deeper
Knows that nulls can crash a sort and that nullsFirst/nullsLast exist to handle them.
Applies nullsFirst/nullsLast to null elements and knows two nulls compare equal.
Distinguishes null-element vs null-key handling, placing the wrapper in the correct slot, and combines it with thenComparing.
Decides between null-tolerant ordering and fail-fast/filtering as a data-quality design choice, and reasons about where NPEs surface in the sort.
## Nulls and ordering Comparison usually dereferences the things being compared (e.g. calls `compareTo`, or a getter for the key). If an element - or an extracted key - is `null`, that dereference throws `NullPointerException` *during* the sort, which is awkward to debug because it surfaces deep inside `TimSort`. ### The wrappers ```java Comparator.nullsFirst(realComparator) // nulls sort BEFORE everything Comparator.nullsLast(realComparator) // nulls sort AFTER everything ``` Semantics of the returned comparator: - `null` vs `null` -> equal (returns 0). - `null` vs non-null -> null is *first* (nullsFirst) or *last* (nullsLast). - non-null vs non-null -> delegates to the wrapped comparator. The wrapped comparator argument may itself be `null`, meaning 'I only need null handling; treat all non-nulls as equal' - rarely what you want, but legal. ### Case 1: null *elements* in the list ```java List<String> names = Arrays.asList("b", null, "a"); names.sort(Comparator.nullsFirst(Comparator.naturalOrder())); // -> [null, a, b] ``` Wrap the **outer** comparator because the elements themselves can be null. ### Case 2: non-null elements with a null *key* Here the element is fine but the field you sort by is null: ```java people.sort( Comparator.comparing(Person::getCity, Comparator.nullsLast(Comparator.naturalOrder()))); ``` The null tolerance belongs to the **key comparator** (the second argument of `comparing`), not the outer comparator - because `Person::getCity` runs on a non-null `Person` and *returns* a possibly-null `String`. A frequent mistake is wrapping the outer comparator instead, which doesn't protect the key extraction and still NPEs. ### Combining with tie-breakers ```java Comparator.comparing(Person::getCity, Comparator.nullsLast(naturalOrder())) .thenComparing(Person::getLastName); ``` Each level handles its own nulls; chaining works as usual, and the object sort remains **stable** (TimSort) so equal/both-null elements keep input order. ### Why not just filter or pre-clean? Sometimes you genuinely want nulls placed deterministically (e.g. records with a missing date shown last in a UI). The wrappers express that intent directly and keep the data intact. If nulls are truly invalid, failing fast (or filtering before the sort) can be the better design - but then do it explicitly, not by accident via an NPE. ### Summary decision - Null **elements** -> wrap the **outer** comparator with nullsFirst/nullsLast. - Null **keys** -> wrap the **key comparator** passed to `comparing`. - Two nulls are equal; the wrapped comparator only sees non-null pairs.
- Your list elements are non-null but getCity() can return null, yet you still get an NPE after using nullsLast on the outer comparator. Why?The outer wrapper only guards against null *elements*. The null is in the extracted key, so the tolerance must go in the key comparator: comparing(Person::getCity, Comparator.nullsLast(naturalOrder())).
- How does nullsFirst order two null elements relative to each other?As equal - it returns 0 for null vs null, so their relative order is preserved by the stable sort.
saying these in an interview costs you the question
- Assuming naturalOrder() handles nulls (it NPEs)
- Wrapping the outer comparator when the NULL is in the key (still NPEs)
- Thinking nullsFirst and nullsLast are the same
- Believing you must remove nulls before sorting (wrappers place them deterministically)