- 1 1. What You Will Learn in This Article (The Shortest Way to Sort a Java List)
- 2 2. Three Common Ways to Sort a List in Java (Which One Should You Use?)
- 3 3. Basic Sorting: Ascending and Descending Order
- 4 4. Sorting a List of Objects: Understanding Comparator
- 5 5. Sorting with Multiple Conditions (The Most Common Real-World Pattern)
- 6 6. Handling null Values Safely (A Very Common Source of Errors)
- 7 7. Common Pitfalls and Mistakes (How to Avoid Subtle Bugs)
- 8 8. Performance Considerations and Choosing the Right Approach
- 9 9. Summary: Java List Sort Cheat Sheet
1. What You Will Learn in This Article (The Shortest Way to Sort a Java List)
When working with Java, it’s extremely common to run into situations where you need to sort a List.
At the same time, many developers—especially beginners—get confused by questions like:
- Which method should I use to sort a List?
- What is the difference between
list.sort()andCollections.sort()? - How do I sort a List of objects instead of simple values?
This article is designed to answer those questions clearly and practically, starting with the conclusion first, then gradually explaining the details and real-world use cases.
1.1 The Conclusion: This Is the Only Pattern You Need to Remember
If you just want the shortest and most standard way to sort a List in Java, this is it:
list.sort(Comparator.naturalOrder());
This sorts the List in ascending order (smallest to largest, A to Z, oldest to newest).
If you want descending order, use this instead:
list.sort(Comparator.reverseOrder());
With just these two lines, you can already sort Lists of:
IntegerStringLocalDate- and most other common types
For many everyday cases, this is all you need.
1.2 Sorting a List of Objects: Specify the Sort Key
In real applications, Lists usually contain objects, not simple values.
For example:
class Person {
private String name;
private int age;
// getters omitted
}To sort a List<Person> by age, write:
list.sort(Comparator.comparing(Person::getAge));
This single line means:
- Extract
agefrom eachPerson - Compare those values
- Sort the List in ascending order
You don’t need to manually implement comparison logic.
Just remember this pattern:
Comparator.comparing(whatToCompare)
1.3 What This Article Covers
This article explains Java List sorting from basics to practical usage, including:
- The difference between
list.sort()andCollections.sort() - How
Comparatorworks in simple terms - Sorting with multiple conditions (for example: age → name)
- Mixing ascending and descending order
- How to safely handle
nullvalues - When to use
stream().sorted()instead oflist.sort()
The goal is not memorization, but understanding why each approach exists.
1.4 Who This Article Is For
This article is written for developers who:
- Understand basic Java syntax (classes, methods, Lists)
- Have used
ArrayListorListbefore - Still have to look up sorting code every time they need it
It is not aimed at absolute beginners who have never written Java code,
but it is beginner-friendly for developers learning practical Java programming.
2. Three Common Ways to Sort a List in Java (Which One Should You Use?)
In Java, there isn’t just one way to sort a List.
In practice, developers mainly use three different approaches, each with slightly different behavior and intent.
Before going deeper into Comparator, it’s important to understand when and why each option exists.
2.1 list.sort(Comparator) — The Modern and Recommended Approach
Since Java 8, this is the standard and most commonly recommended way to sort a List.
list.sort(Comparator.naturalOrder());Key characteristics
- Defined directly on the
Listinterface - Clear and readable
- Sorts the original List in place (destructive)
Sorting objects works the same way:
list.sort(Comparator.comparing(Person::getAge));When to use it
- When you are okay with modifying the original List
- When you want the clearest and simplest solution
👉 If you’re unsure, list.sort() is usually the right choice.
2.2 Collections.sort(list) — Older Style You Still Need to Recognize
You may see code like this in older tutorials or legacy projects:
Collections.sort(list);Or with a Comparator:
Collections.sort(list, Comparator.reverseOrder());Key characteristics
- Exists since Java 1.2
- Internally behaves almost the same as
list.sort - Also modifies the original List
Why it’s less common today
- Java 8 introduced
list.sort, which feels more natural - Sorting a List via
Collectionsis less intuitive
For new code, list.sort is preferred.
However, understanding Collections.sort is still important when reading older codebases.
2.3 stream().sorted() — Non-Destructive Sorting
The third option uses the Stream API.
List<Integer> sorted =
list.stream()
.sorted()
.toList();With a Comparator:
List<Person> sorted =
list.stream()
.sorted(Comparator.comparing(Person::getAge))
.toList();Key characteristics
- Does NOT modify the original List
- Returns a new sorted List
- Easy to combine with
filter,map, and other stream operations
When to use it
- When you must keep the original List unchanged
- When sorting is part of a data processing pipeline
For simple sorting, however, list.sort is usually clearer and more efficient.
2.4 How to Choose (Quick Decision Guide)
| Goal | Recommended Method |
|---|---|
| Sort a List directly | list.sort() |
| Understand or maintain old code | Collections.sort() |
| Keep the original List unchanged | stream().sorted() |
At this point, remember just one rule:
Use
list.sort()unless you have a clear reason not to.
3. Basic Sorting: Ascending and Descending Order
Now that you know which sorting method to use, let’s focus on the most common requirement:
sorting in ascending or descending order.
This section covers Lists of basic types such as numbers, strings, and dates—the foundation for everything that follows.
3.1 Sorting in Ascending Order (Natural Order)
Many Java types have a natural order, such as:
- Numbers → smaller to larger
- Strings → alphabetical order (A to Z)
- Dates → older to newer
To sort a List in ascending order, use:
list.sort(Comparator.naturalOrder());Example: Sorting numbers
List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.naturalOrder());Result:
[1, 2, 3, 5]Example: Sorting strings
List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.naturalOrder());Result:
[Alice, Bob, Tom]👉 If you just want ascending order, this is the simplest and safest approach.
3.2 Sorting in Descending Order
To reverse the natural order, use Comparator.reverseOrder().
list.sort(Comparator.reverseOrder());Example: Numbers in descending order
List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.reverseOrder());Result:
[5, 3, 2, 1]Example: Strings in descending order
List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.reverseOrder());Result:
[Tom, Bob, Alice]3.3 When the Comparator Can Be Omitted
In some cases, you may see sorting code written without an explicit Comparator.
Collections.sort(list);Or even:
list.sort(null);These work only if:
- The elements implement
Comparable - You want natural (ascending) order
While valid, these styles are less explicit.
In real-world codebases, this version is usually preferred for clarity:
list.sort(Comparator.naturalOrder());3.4 A Common Misunderstanding: Who Decides the Order?
A frequent source of confusion for beginners is thinking that:
sort()decides ascending or descending order
In reality:
sort()performs the sortingComparatordefines how elements are compared
Once you understand this separation of responsibilities,
everything else—object sorting, multiple conditions, and null handling—becomes much easier.
4. Sorting a List of Objects: Understanding Comparator
In real-world Java applications, you rarely sort simple values like Integer or String.
Most of the time, you’ll be sorting Lists of custom objects.
This is where Comparator becomes the core concept.
4.1 What Is a Comparator?
A Comparator defines how two elements should be compared.
Conceptually, it answers this question:
“Given two objects, which one should come first?”
Internally, a Comparator returns:
- A negative number → first element comes first
- Zero → order does not matter
- A positive number → second element comes first
Fortunately, in modern Java you almost never need to implement this logic manually.
4.2 Sorting by a Field with Comparator.comparing
Consider the following class:
class Person {
private String name;
private int age;
// getters omitted
}To sort a List<Person> by age, use:
list.sort(Comparator.comparing(Person::getAge));
This reads naturally:
- Take each
Person - Extract the
age - Compare those values
- Sort the List in ascending order
This single line replaces many lines of older comparison code.
4.3 Sorting by Strings, Dates, and Other Types
The same pattern works for almost any field type.
Sort by name
list.sort(Comparator.comparing(Person::getName));Sort by date
list.sort(Comparator.comparing(Person::getBirthDate));As long as the extracted value has a natural order,Comparator.comparing will work without additional setup.
4.4 Using Primitive-Specific Comparators
For numeric fields, Java provides optimized methods:
comparingIntcomparingLongcomparingDouble
Example:
list.sort(Comparator.comparingInt(Person::getAge));These methods:
- Avoid unnecessary object boxing
- Make your intent clearer
- Are slightly more efficient for large Lists
While the difference is small, they are considered best practice for numeric fields.
4.5 Why This Matters
Once you understand Comparator.comparing, you unlock:
- Multi-condition sorting
- Mixed ascending and descending order
- Safe handling of
nullvalues
In other words, this is the foundation of practical List sorting in Java.
5. Sorting with Multiple Conditions (The Most Common Real-World Pattern)
In real applications, sorting by a single field is often not enough.
You usually need secondary and tertiary conditions to create a stable and meaningful order.
Examples include:
- Sort by age, then by name
- Sort by priority, then by timestamp
- Sort by score (descending), then by ID (ascending)
Java’s Comparator API is designed specifically for this.
5.1 The Basics of thenComparing
Multiple-condition sorting follows a simple rule:
If two elements are equal by the first condition, use the next condition.
Here’s the basic pattern:
list.sort(
Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName)
);This means:
- Sort by
age(ascending) - If ages are equal, sort by
name(ascending)
This produces a consistent and predictable order.
5.2 Mixing Ascending and Descending Order
Very often, you want one field in descending order and another in ascending order.
Example: Score (descending), Name (ascending)
list.sort(
Comparator.comparingInt(Person::getScore).reversed()
.thenComparing(Person::getName)
);Important detail:
reversed()only applies to the Comparator immediately before it
This makes it safe to combine different sort directions.
5.3 Passing a Comparator to thenComparing
For better readability, you can explicitly define the sort direction inside thenComparing.
Example: Age (ascending), Registration Date (descending)
list.sort(
Comparator.comparingInt(Person::getAge)
.thenComparing(
Comparator.comparing(Person::getRegisterDate).reversed()
)
);This style makes it very clear which fields are ascending or descending,
which is helpful for code reviews and long-term maintenance.
5.4 A Realistic Business Example
list.sort(
Comparator.comparingInt(Order::getPriority)
.thenComparing(Order::getDeadline)
.thenComparing(Order::getOrderId)
);Sorting logic:
- Higher priority first
- Earlier deadline first
- Lower order ID first
This ensures a stable and business-friendly order.

5.5 Keep Multi-Condition Sorting Readable
As sorting logic grows, readability becomes more important than brevity.
Best practices:
- Break lines for each condition
- Avoid deeply nested lambdas
- Add comments if business rules are not obvious
Clear sorting logic saves time for everyone who reads the code later.
6. Handling null Values Safely (A Very Common Source of Errors)
When sorting real-world data, null values are almost unavoidable.
Fields may be optional, legacy data may be incomplete, or values may simply be missing.
If you don’t handle null explicitly, sorting can easily fail at runtime.
6.1 Why null Causes Problems When Sorting
Consider this code:
list.sort(Comparator.comparing(Person::getName));If getName() returns null for any element, Java will throw aNullPointerException during comparison.
This happens because:
- A
Comparatorassumes values can be compared nullhas no natural order unless you define one
Therefore, null handling must be explicit.
6.2 Using nullsFirst and nullsLast
Java provides helper methods to define how null values should be ordered.
Place null values first
list.sort(
Comparator.comparing(
Person::getName,
Comparator.nullsFirst(Comparator.naturalOrder())
)
);Place null values last
list.sort(
Comparator.comparing(
Person::getName,
Comparator.nullsLast(Comparator.naturalOrder())
)
);These approaches:
- Prevent
NullPointerException - Make the sorting rule explicit and readable
6.3 When the List Itself Contains null Elements
Sometimes, the elements of the List themselves may be null.
List<Person> list = Arrays.asList(
new Person("Alice", 20),
null,
new Person("Bob", 25)
);To handle this safely:
list.sort(
Comparator.nullsLast(
Comparator.comparing(Person::getName)
)
);This ensures:
nullelements are moved to the end- Non-null elements are sorted normally
6.4 Be Careful with comparingInt and null
Primitive-specific comparators like comparingInt cannot handle null.
Comparator.comparingInt(Person::getAge); // age must be intIf the field is an Integer that may be null, use:
Comparator.comparing(
Person::getAge,
Comparator.nullsLast(Integer::compare)
);This avoids unexpected runtime errors.
6.5 Treat null Handling as Part of the Specification
Deciding whether null values should appear:
- At the beginning
- At the end
- Or be filtered out entirely
is a business decision, not just a technical one.
By using nullsFirst or nullsLast, you document that decision directly in code—
making your sorting logic safer and easier to understand.
7. Common Pitfalls and Mistakes (How to Avoid Subtle Bugs)
Sorting a List in Java looks simple, but there are several easy-to-miss pitfalls that can lead to bugs, unexpected behavior, or performance issues.
Understanding these in advance will save you time during debugging and code reviews.
7.1 Forgetting That Sorting Is Destructive
Both list.sort() and Collections.sort() modify the original List.
List<Integer> original = new ArrayList<>(List.of(3, 1, 2));
List<Integer> alias = original;
original.sort(Comparator.naturalOrder());In this case:
originalis sortedaliasis also sorted (because they reference the same List)
How to avoid this
If you need to preserve the original order:
List<Integer> sorted = new ArrayList<>(original);
sorted.sort(Comparator.naturalOrder());Or use streams:
List<Integer> sorted =
original.stream()
.sorted()
.toList();Always ask yourself:
“Is it okay to modify the original List?”
7.2 Comparator Consistency and Stable Ordering
A Comparator should produce consistent and predictable results.
Example:
Comparator.comparing(Person::getAge);If multiple people have the same age, their relative order is undefined.
This may be acceptable—but often it is not.
Best practice
Add a secondary condition to stabilize the order:
Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getId);This ensures the sorting result is deterministic.
7.3 Case Sensitivity in String Sorting
String’s natural order is case-sensitive.
List<String> list = List.of("apple", "Banana", "orange");
list.sort(Comparator.naturalOrder());This may produce results that feel unintuitive.
Case-insensitive sorting
list.sort(String.CASE_INSENSITIVE_ORDER);Before choosing, consider:
- Is this for display?
- Or for internal logic?
The answer determines the correct approach.
7.4 Doing Heavy Work Inside a Comparator
A Comparator may be called many times during sorting.
Avoid:
- Database access
- Network calls
- Expensive calculations
// Bad idea (conceptual example)
Comparator.comparing(p -> expensiveOperation(p));Better approach
- Precompute values
- Store them in fields
- Compare simple, cheap values
Efficient comparators make a big difference with large Lists.
7.5 Prioritize Readability Over Cleverness
Sorting logic is often read more times than it is written.
Instead of:
- One long chained expression
- Deeply nested lambdas
Prefer:
- Line breaks
- Clear structure
- Optional comments for business rules
Readable sorting code reduces bugs and makes maintenance easier.
8. Performance Considerations and Choosing the Right Approach
By now, you know how to sort Lists in Java.
This section focuses on which approach to choose from a performance and design perspective.
In most applications, sorting is not a bottleneck—but bad choices can still cause unnecessary overhead.
8.1 list.sort() vs stream().sorted()
This is the most common decision point.
list.sort()
list.sort(Comparator.comparingInt(Person::getAge));Pros
- No extra List allocation
- Clear intent: “sort this List”
- Slightly more efficient
Cons
- Modifies the original List
stream().sorted()
List<Person> sorted =
list.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.toList();Pros
- Original List remains unchanged
- Fits naturally into stream pipelines
Cons
- Allocates a new List
- Slightly more overhead
Practical rule
- Simple sorting →
list.sort() - Transformation pipelines or immutability →
stream().sorted()
8.2 Sorting Large Lists Efficiently
Sorting algorithms call the Comparator many times.
For large Lists, this matters.
Key guidelines
- Keep comparators lightweight
- Avoid method chains that do heavy work
- Prefer primitive comparators (
comparingInt, etc.)
Example: Precompute expensive keys
Instead of:
Comparator.comparing(p -> calculateScore(p));Do:
// Precompute once
p.setScore(calculateScore(p));Then sort by the field:
Comparator.comparingInt(Person::getScore);This dramatically reduces repeated work during sorting.
8.3 Is Collections.sort() Ever the Right Choice?
For new code, almost never.
However, it still appears in:
- Legacy projects
- Older tutorials
- Java 7 and earlier codebases
You don’t need to use it—but you should recognize it.
8.4 Recommended Decision Checklist
Before sorting, ask:
- Can I modify the original List?
- Do I need multiple sort conditions?
- Can any fields be
null? - Is performance important at scale?
Answering these questions naturally leads to the correct solution.
9. Summary: Java List Sort Cheat Sheet
Let’s wrap up everything into a quick-reference guide you can rely on.
9.1 Quick Patterns You’ll Use Most Often
Ascending order
list.sort(Comparator.naturalOrder());Descending order
list.sort(Comparator.reverseOrder());9.2 Sorting Objects by a Field
list.sort(Comparator.comparing(Person::getName));For numbers:
list.sort(Comparator.comparingInt(Person::getAge));9.3 Multiple Conditions
list.sort(
Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName)
);Mixed order:
list.sort(
Comparator.comparingInt(Person::getScore).reversed()
.thenComparing(Person::getName)
);9.4 Safe null Handling
list.sort(
Comparator.comparing(
Person::getName,
Comparator.nullsLast(Comparator.naturalOrder())
)
);List may contain null elements:
list.sort(
Comparator.nullsLast(
Comparator.comparing(Person::getName)
)
);9.5 Non-Destructive Sorting
List<Person> sorted =
list.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.toList();9.6 Final Takeaway
Java List sorting becomes simple once you remember:
Comparatordefines the ordersort()performs the operation- Clarity beats cleverness
- Explicit
nullhandling prevents bugs
If you internalize these principles,
you’ll never need to “re-learn” Java List sorting again.

