How to Sort a List in Java: list.sort, Comparator, Multiple Conditions, and Null Handling

目次

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() and Collections.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:

  • Integer
  • String
  • LocalDate
  • 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 age from each Person
  • 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() and Collections.sort()
  • How Comparator works in simple terms
  • Sorting with multiple conditions (for example: age → name)
  • Mixing ascending and descending order
  • How to safely handle null values
  • When to use stream().sorted() instead of list.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 ArrayList or List before
  • 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 List interface
  • 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 Collections is 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)

GoalRecommended Method
Sort a List directlylist.sort()
Understand or maintain old codeCollections.sort()
Keep the original List unchangedstream().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 sorting
  • Comparator defines 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:

  • comparingInt
  • comparingLong
  • comparingDouble

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 null values

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:

  1. Sort by age (ascending)
  2. 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:

  1. Higher priority first
  2. Earlier deadline first
  3. 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 a
NullPointerException during comparison.

This happens because:

  • A Comparator assumes values can be compared
  • null has 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:

  • null elements 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 int

If 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:

  • original is sorted
  • alias is 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 sortinglist.sort()
  • Transformation pipelines or immutabilitystream().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:

  1. Can I modify the original List?
  2. Do I need multiple sort conditions?
  3. Can any fields be null?
  4. 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:

  • Comparator defines the order
  • sort() performs the operation
  • Clarity beats cleverness
  • Explicit null handling prevents bugs

If you internalize these principles,
you’ll never need to “re-learn” Java List sorting again.