Java example

How to sort a list of objects by property in Java

7 min read Updated Sep 2026 Runs in an isolated runtime
Quick answer

Pass List.sort a comparator built from the property: people.sort(Comparator.comparing(Person::name)). Use Comparator.comparingInt(Person::age) for an int field, add .reversed() for descending order and .thenComparing(...) for a tie-breaker. For a sorted copy that leaves the list alone, use people.stream().sorted(comparator).toList() (Java 16+).

A List<Person> has no idea how one Person compares to another, so you tell it with a Comparator<Person>: a function that takes two elements and returns a negative number, zero or a positive number. Since Java 8 you rarely write that function yourself. The static factories on Comparator build it from the property you want to sort by, and they chain for descending order, tie-breaks, case-insensitive text and null values. This page covers those, the Comparable interface for a type's built-in order, sorting a copy instead of the list itself, and the subtraction comparator that still shows up in old answers and overflows. The data types are records (Java 16+). Each example runs on this page: hit Run, then edit the code and run it again.

1list.sort(Comparator.comparing(...))Recommended

Comparator.comparing(Person::name) takes a key extractor, a function that reads the property, and returns a comparator that compares the keys. The key can be any Comparable type: String, Integer, LocalDate, BigDecimal, an enum. For primitive fields, comparingInt, comparingLong and comparingDouble compare the raw values instead of boxing each one into an Integer or Double on every comparison. List.sort reorders the list in place.

Main.java

Output

Prints [Ada, Barbara, Grace, Linus] and the first record, Person[name=Ada, age=36, height=1.65, joined=2019-11-02]. Then [Linus, Ada, Grace, Barbara] youngest first, [Linus, Barbara, Grace, Ada] tallest first, and the four join dates from 2018-01-09 Barbara to 2023-06-30 Linus. With a class instead of a record the method reference is Person::getName. The key extractor can be any lambda, so Comparator.comparing(c -> c.address().city()) sorts by a nested property. The long-hand equivalent of the first sort is people.sort((a, b) -> a.name().compareTo(b.name())), which is what many older answers show.

2Multiple fields: thenComparing and reversed()

thenComparing adds a tie-breaker: the second key is only consulted when the first one compares equal. There are primitive versions too (thenComparingInt and friends), and a two-argument form, thenComparing(key, comparator), that sorts just that key in its own order. The one thing to watch is where .reversed() goes: it reverses the whole comparator it is called on, which includes every key chained before it.

Main.java

Output

The first block prints 90 alice, 90 dana, 70 bob, 70 carol: the .reversed() sits right after the score key, so only the score is descending. The second prints blue 90 alice, blue 70 bob, red 90 dana, red 70 carol. The last prints red dana, red carol, blue bob, blue alice: both the team and the name came out descending, because the trailing .reversed() applied to the whole chain. List.sort is guaranteed stable, so elements that compare equal on every key keep the order they already had.

3Case-insensitive text and null values

Two things break a plain comparing(...) on real data. String's natural order compares character codes, so every uppercase letter sorts before every lowercase one. And a null property makes the comparator throw a NullPointerException. Both have a fix in the same place: the second argument of comparing, which says how to compare the extracted keys.

Main.java

Output

Prints [Alice, Dave, bob, carol] with the natural order and [Alice, bob, carol, Dave] case-insensitively, then comparing(Contact::city) threw NullPointerException. With nullsLast the cities print Austin Dave, Berlin carol, Oslo bob, null Alice, and the last line is null, then Alice. Note the two positions: nullsLast inside comparing handles a null property, nullsFirst around the whole comparator handles a null element. CASE_INSENSITIVE_ORDER only folds case; for accents and language rules, pass a java.text.Collator such as Collator.getInstance(Locale.FRENCH) as the key comparator instead.

4Comparable (natural order) and the subtraction bug

If a type has one obvious order, such as version numbers or money, make it implement Comparable<T> and put the order in compareTo. Then Collections.sort(list), list.sort(null), TreeSet, Collections.max and Comparator.reverseOrder() all use it without being told how. Records do not implement Comparable on their own. When you write compareTo or a compare lambda by hand, compare with Integer.compare, never by subtracting: the subtraction can overflow and flip the sign.

Main.java

Output

Prints [1.2, 1.10, 2.0], [2.0, 1.10, 1.2] and 2.0: numeric order, so 1.10 comes after 1.2, which a string sort would get wrong. The subtraction comparator leaves the accounts as [A, B, C], with two billion before minus two billion, because 2_000_000_000 - (-2_000_000_000) is -294967296 in int arithmetic, so it reported A as smaller than B. Integer.compare gives the correct [B, C, A]. Building compareTo from a static final Comparator, as Version does, avoids hand-written comparisons entirely. Keep natural order consistent with equals where you can: a TreeSet treats two elements that compare as 0 as duplicates.

5Sort a copy: stream().sorted().toList()

list.sort and Collections.sort change the list they are given. When the original order still matters, or the list is unmodifiable (List.of, Stream.toList(), a Collections.unmodifiableList view), sort a copy. A stream does it in one expression, and Stream.toList() (Java 16+) collects the result. For the same order backwards, Java 21 added List.reversed(), a reverse-order view that copies and sorts nothing.

Main.java

Output

Prints [Foundation, Dune, Neuromancer], then the untouched source [Dune, Neuromancer, Foundation], then sort failed: java.lang.UnsupportedOperationException, then [Dune, Foundation, Neuromancer] from the copy and [Neuromancer, Dune, Foundation] from the reversed view. The list from toList() is unmodifiable too; on Java 8 to 15, or when you need a list you can add to, collect with Collectors.toList() or new ArrayList<>(...). Both approaches copy only the list: the records inside are the same objects. Building the list in the first place is covered in initialize a list in one line.

6Which should you use?

ComparatorNeedsBest for
list.sort(Comparator.comparing(Person::name))Java 8+Strings, dates, any Comparable property: the default
Comparator.comparingInt(Person::age)Java 8+int, long and double properties without boxing
.reversed() / thenComparing(key, Comparator.reverseOrder())Java 8+Descending order, for the whole chain or one key
.thenComparing(Person::name)Java 8+Tie-breaks, sorting by several fields
comparing(key, String.CASE_INSENSITIVE_ORDER)Java 8+Names and titles people read
comparing(key, Comparator.nullsLast(Comparator.naturalOrder()))Java 8+Properties that can be null
implements Comparable<T> + Collections.sort(list)Any versionTypes with one obvious order (versions, money)
list.stream().sorted(cmp).toList()Java 16+A sorted copy; the source stays as it was
(a, b) -> a.age() - b.age()AvoidNothing: it overflows; use Integer.compare or comparingInt

Frequently asked questions

How do I sort an ArrayList of objects before Java 8?

Pass Collections.sort an anonymous Comparator: Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person a, Person b) { return a.getName().compareTo(b.getName()); } });. It still compiles and works. Since Java 8 the same sort is people.sort(Comparator.comparing(Person::getName)), and Collections.sort(list, c) simply calls list.sort(c).

How do I sort a list of objects in descending order?

Call .reversed() on the comparator: people.sort(Comparator.comparingInt(Person::age).reversed()). To reverse one key and not the rest, pass Comparator.reverseOrder() as the key comparator: Comparator.comparing(Person::name, Comparator.reverseOrder()). Collections.reverseOrder(cmp) also reverses a comparator. Remember that .reversed() at the end of a thenComparing chain reverses every key in it.

Why does Comparator.comparing(p -> p.name()).reversed() not compile?

Java infers the lambda parameter type from the target type, but .reversed() is called on the result of comparing(...) first, so there is no target type yet and p becomes Object. javac reports error: cannot find symbol, symbol: method name(), location: variable p of type java.lang.Object. Use a method reference (Comparator.comparing(Person::name).reversed()), type the parameter ((Person p) -> p.name()), or add a type witness (Comparator.<Person, String>comparing(p -> p.name())). Without .reversed(), people.sort(Comparator.comparing(p -> p.name())) compiles fine.

Is List.sort stable in Java?

Yes. The List.sort and Collections.sort documentation guarantees a stable sort: elements that compare equal keep their original relative order. The implementation is TimSort (for objects), which is also fast on lists that are already partly sorted. Stream.sorted is stable for ordered streams, such as a stream over a List. Stability is what lets thenComparing chains and repeated sorts behave predictably on ties.

What does "Comparison method violates its general contract!" mean?

TimSort throws java.lang.IllegalArgumentException: Comparison method violates its general contract! when it notices that your comparator contradicts itself, for example saying a < b, b < c and c < a. The usual causes are a subtracting comparator that overflows, a comparator that reads mutable or random state, or one that mishandles nulls or NaN. Sorting 5000 random ints with (a, b) -> a - b triggers it for some inputs. Replace hand-written comparisons with Integer.compare or Comparator.comparingInt.

Should I implement Comparable or use a Comparator?

Implement Comparable only when the type has one natural order that every caller would expect, such as a version number or an amount of money; then Collections.sort(list), TreeSet and Collections.max work without arguments. For everything else, and for any type you do not own, pass a Comparator: you can have as many as you like, one per sort order. A compareTo can itself delegate to a static final Comparator built with comparingInt(...).thenComparing(...). Sorting a type that is not Comparable with no comparator fails: Collections.sort(list) does not compile, and list.sort(null) compiles but throws java.lang.ClassCastException: class Main$Person cannot be cast to class java.lang.Comparable.

Run it yourself

Open any of these in the full Java editor: tweak, run, and share.

Java playground