Java Stream sorted() Method
Java Stream sorted() Method
sorted() is the intermediate operation that arranges a stream's elements in order before the rest of the pipeline sees them, using either their natural ordering through Comparable or a custom rule supplied as a Comparator. It behaves differently from filter() and map() in one important way: it is stateful. filter() and map() decide what to do with each element the instant it arrives, but sorted() cannot produce even its first result until it has seen every single element in the stream, because deciding where one element belongs requires knowing everything else it might come before or after.
What Is sorted()?
sorted() comes in two forms. Stream<T> sorted() uses the elements' natural ordering, calling compareTo() from Comparable<T> — this throws ClassCastException at the point elements are actually compared if T does not implement Comparable. Stream<T> sorted(Comparator<? super T> comparator) uses a supplied Comparator instead, working for any type regardless of whether it implements Comparable, and allowing the same class to be sorted differently depending on what a given pipeline actually needs.
Being stateful has real consequences. sorted() has to buffer the entire stream internally before it can emit anything, which means it does not work on an infinite stream unless that stream is bounded first, and it generally costs more — both in memory and typically in O(n log n) time — than a stateless operation like filter or map, which never need to hold more than one element at a time.
Why sorted() Was Introduced
Sorting a collection used to mean a separate, imperative step — calling Collections.sort() on a mutable list before or after whatever processing actually needed to happen, breaking the flow of the rest of the logic.
1// File: BeforeSorted.java
2import java.util.*;
3
4public class BeforeSorted {
5 record Player(String name, int score) {}
6
7 public static void main(String[] args) {
8 List<Player> players = new ArrayList<>(List.of(
9 new Player("Rohit", 82),
10 new Player("Ananya", 95),
11 new Player("Vikram", 67)
12 ));
13
14 Collections.sort(players, (first, second) -> second.score() - first.score());
15
16 for (Player player : players) {
17 System.out.println(player.name() + " - " + player.score());
18 }
19 }
20}Output:
Ananya - 95
Rohit - 82
Vikram - 67
sorted() folds the same ordering directly into the pipeline, and it never needs a mutable list to work with — it operates on whatever stream it receives, even one built from an immutable source.
1// File: AfterSorted.java
2import java.util.*;
3import java.util.stream.*;
4
5public class AfterSorted {
6 record Player(String name, int score) {}
7
8 public static void main(String[] args) {
9 List<Player> players = List.of(
10 new Player("Rohit", 82),
11 new Player("Ananya", 95),
12 new Player("Vikram", 67)
13 );
14
15 players.stream()
16 .sorted(Comparator.comparingInt(Player::score).reversed())
17 .forEach(player -> System.out.println(player.name() + " - " + player.score()));
18 }
19}Output:
Ananya - 95
Rohit - 82
Vikram - 67
Notice players in the second version is built with List.of(), an immutable list Collections.sort() could never have sorted directly. sorted() never touches its source at all — it produces a new, ordered stream instead of rearranging anything in place.
Syntax
Natural ordering requires Comparable; a supplied Comparator works regardless, and Comparator.reverseOrder() is a quick way to flip natural ordering around.
1// File: SortedSyntaxForms.java
2import java.util.*;
3import java.util.stream.*;
4
5public class SortedSyntaxForms {
6 public static void main(String[] args) {
7 List<Integer> scores = List.of(82, 95, 67, 95, 40);
8
9 // Natural ordering - requires elements to implement Comparable
10 List<Integer> naturalOrder = scores.stream()
11 .sorted()
12 .collect(Collectors.toList());
13
14 // Custom ordering via a Comparator, reversed for descending order
15 List<Integer> descendingOrder = scores.stream()
16 .sorted(Comparator.reverseOrder())
17 .collect(Collectors.toList());
18
19 System.out.println("Natural order: " + naturalOrder);
20 System.out.println("Descending order: " + descendingOrder);
21 }
22}Output:
Natural order: [40, 67, 82, 95, 95]
Descending order: [95, 95, 82, 67, 40]
Common Use Cases
Sorting Objects by a Field
Comparator.comparing() and its primitive-specialized siblings — comparingInt, comparingDouble — combined with a getter reference are the standard way to sort a stream of objects by one field.
1// File: SortByFieldExample.java
2import java.util.*;
3import java.util.stream.*;
4
5public class SortByFieldExample {
6 record Player(String name, int score) {}
7
8 public static void main(String[] args) {
9 List<Player> players = List.of(
10 new Player("Rohit", 82),
11 new Player("Ananya", 95),
12 new Player("Vikram", 67)
13 );
14
15 List<String> namesByScore = players.stream()
16 .sorted(Comparator.comparingInt(Player::score))
17 .map(Player::name)
18 .collect(Collectors.toList());
19
20 System.out.println(namesByScore);
21 }
22}Output:
[Vikram, Rohit, Ananya]
Breaking Ties With thenComparing
Comparator.thenComparing() applies a second rule only when the first rule considers two elements equal, which is exactly what a tie-breaker needs.
1// File: ThenComparingExample.java
2import java.util.*;
3import java.util.stream.*;
4
5public class ThenComparingExample {
6 record Player(String name, int score) {}
7
8 public static void main(String[] args) {
9 List<Player> players = List.of(
10 new Player("Rohit", 95),
11 new Player("Ananya", 95),
12 new Player("Vikram", 67)
13 );
14
15 players.stream()
16 .sorted(Comparator.comparingInt(Player::score).reversed()
17 .thenComparing(Player::name))
18 .forEach(player -> System.out.println(player.name() + " - " + player.score()));
19 }
20}Output:
Ananya - 95
Rohit - 95
Vikram - 67
Combining sorted() With limit() for a Top-N Query
Sorting first and then limiting is the standard shape of a top-N query — the ordering has to exist before a "top" slice of it means anything.
1// File: TopNWithSortedExample.java
2import java.util.*;
3import java.util.stream.*;
4
5public class TopNWithSortedExample {
6 record Player(String name, int score) {}
7
8 public static void main(String[] args) {
9 List<Player> players = List.of(
10 new Player("Rohit", 82),
11 new Player("Ananya", 95),
12 new Player("Vikram", 67),
13 new Player("Priya", 88),
14 new Player("Karan", 71)
15 );
16
17 List<String> top3 = players.stream()
18 .sorted(Comparator.comparingInt(Player::score).reversed())
19 .limit(3)
20 .map(Player::name)
21 .collect(Collectors.toList());
22
23 System.out.println(top3);
24 }
25}Output:
[Ananya, Priya, Rohit]
Sorting by a Rule Other Than Natural Ordering
A Comparator lets a stream be sorted by any measurable property at all, not just the property its natural ordering already uses.
1// File: NonNaturalOrderingExample.java
2import java.util.*;
3import java.util.stream.*;
4
5public class NonNaturalOrderingExample {
6 public static void main(String[] args) {
7 List<String> teamNames = List.of("Titans", "Warriors", "Kings", "Super Giants");
8
9 List<String> byLength = teamNames.stream()
10 .sorted(Comparator.comparingInt(String::length))
11 .collect(Collectors.toList());
12
13 System.out.println(byLength);
14 }
15}Output:
[Kings, Titans, Warriors, Super Giants]
Real-World Example
A fantasy sports app needs to rank contest participants by total points, break ties by whoever submitted their team earlier, and show only the top few with a rank number attached to each one. Getting the tie-break rule right matters here more than it might seem — two participants finishing with the exact same score need a deterministic, reproducible order every time the leaderboard is rebuilt, not whatever order the sort happens to leave them in.
1// File: Participant.java
2
3public record Participant(String name, int points, int submissionOrder) {}1// File: LeaderboardEntry.java
2
3public record LeaderboardEntry(int rank, String name, int points) {}1// File: LeaderboardService.java
2import java.util.*;
3import java.util.stream.*;
4
5public class LeaderboardService {
6
7 public List<LeaderboardEntry> buildLeaderboard(List<Participant> participants, int topN) {
8 List<Participant> ranked = participants.stream()
9 .sorted(Comparator.comparingInt(Participant::points).reversed()
10 .thenComparing(Participant::submissionOrder))
11 .limit(topN)
12 .collect(Collectors.toList());
13
14 List<LeaderboardEntry> entries = new ArrayList<>();
15 for (int i = 0; i < ranked.size(); i++) {
16 entries.add(new LeaderboardEntry(i + 1, ranked.get(i).name(), ranked.get(i).points()));
17 }
18 return entries;
19 }
20}1// File: LeaderboardDemo.java
2import java.util.*;
3
4public class LeaderboardDemo {
5 public static void main(String[] args) {
6 List<Participant> participants = List.of(
7 new Participant("Rohit", 340, 3),
8 new Participant("Ananya", 385, 1),
9 new Participant("Vikram", 210, 5),
10 new Participant("Priya", 385, 2),
11 new Participant("Karan", 298, 4)
12 );
13
14 LeaderboardService leaderboardService = new LeaderboardService();
15 List<LeaderboardEntry> top3 = leaderboardService.buildLeaderboard(participants, 3);
16
17 top3.forEach(entry ->
18 System.out.println("#" + entry.rank() + " " + entry.name() + " - " + entry.points() + " pts"));
19 }
20}Output:
#1 Ananya - 385 pts
#2 Priya - 385 pts
#3 Rohit - 340 pts
Ananya and Priya both finish with 385 points, and thenComparing(Participant::submissionOrder) is what decides Ananya ranks first — she submitted earlier. During code reviews, seniors commonly flag a leaderboard built without an explicit tie-break rule, because Comparator.thenComparing here is not a nice-to-have; it is what makes the ranking deterministic every single time the leaderboard gets rebuilt from the same data.
Combining sorted() With Other Features
sorted() takes a Comparator, built exactly the way comparing, thenComparing, and reversed compose in every example above. It pairs naturally with limit() for top-N queries, but the stateful nature of sorted() means the entire stream is still buffered and fully sorted before limit() takes its slice — there is no shortcut where only the top few elements actually get computed without sorting everything else first. sorted() cannot be combined meaningfully with an infinite stream from Stream.generate() or Stream.iterate() unless that stream is bounded with limit() before sorted() runs, since sorted() needs a finite, complete stream to do its job at all.
Best Practices
Build multi-level sort rules with Comparator.comparing() (or its primitive-specialized variants) chained with thenComparing(), rather than writing a compareTo() implementation by hand with nested if-else branches — the chained form reads as a direct description of the ranking rule.
Apply limit() after sorted() when only the top few results matter for display, and keep in mind that sorted() still processes and orders every element in the stream first — it is not a shortcut past a full sort, even when the final result only shows three of them.
Never chain sorted() onto a genuinely infinite stream without bounding it with limit() first. sorted() will simply never produce a result on an unbounded source, since it is always waiting to see everything before it can start.
Reach for sorted(Comparator) rather than relying on natural ordering with sorted() whenever a class does not implement Comparable, or when the ordering a given pipeline needs differs from that class's natural one.
Common Mistakes
Calling sorted() with no arguments on a type that does not implement Comparable throws ClassCastException the moment elements are actually compared — records do not implement Comparable automatically just because they exist.
1// File: SortedNotComparableMistake.java
2import java.util.*;
3import java.util.stream.*;
4
5public class SortedNotComparableMistake {
6 record Player(String name, int score) {}
7
8 public static void main(String[] args) {
9 List<Player> players = List.of(
10 new Player("Rohit", 82),
11 new Player("Ananya", 95)
12 );
13
14 try {
15 List<Player> sortedPlayers = players.stream()
16 .sorted()
17 .collect(Collectors.toList());
18 System.out.println("Never printed: " + sortedPlayers);
19 } catch (ClassCastException e) {
20 System.out.println("ClassCastException - Player does not implement Comparable");
21 }
22
23 // Supplying a Comparator works regardless of whether Player is Comparable
24 List<String> sortedNames = players.stream()
25 .sorted(Comparator.comparingInt(Player::score))
26 .map(Player::name)
27 .collect(Collectors.toList());
28
29 System.out.println("Sorted by Comparator: " + sortedNames);
30 }
31}Output:
ClassCastException - Player does not implement Comparable
Sorted by Comparator: [Rohit, Ananya]
Assuming that following sorted() with limit() makes the sort itself cheaper is a common but incorrect mental model. sorted() still buffers and orders the entire stream before limit() ever gets a chance to discard anything — for genuinely large datasets where only a handful of top results are ever needed, a bounded priority queue built by hand, rather than a full stream sort, is the structure that actually avoids paying for work beyond what the top-N query needs.
Feeding an infinite stream straight into sorted() without bounding it first leaves the pipeline waiting forever, since sorted() can never see "everything" from a source with no end.
1// File: InfiniteStreamSortedMistake.java
2import java.util.*;
3import java.util.stream.*;
4
5public class InfiniteStreamSortedMistake {
6 public static void main(String[] args) {
7 // Stream.iterate(1, n -> n + 1).sorted().limit(5) would never terminate -
8 // sorted() waits for every element from an infinite source before
9 // producing anything at all, so this line is intentionally NOT run here
10
11 // Bounding the stream FIRST, then sorting the now-finite result, works fine
12 List<Integer> boundedThenSorted = Stream.iterate(1, n -> n + 1)
13 .limit(5)
14 .sorted(Comparator.reverseOrder())
15 .collect(Collectors.toList());
16
17 System.out.println(boundedThenSorted);
18 }
19}Output:
[5, 4, 3, 2, 1]
Interview Questions
Q1. What does sorted() do, and what are its two overloaded forms?
sorted() returns a new stream with the same elements arranged in order. The no-argument form, sorted(), uses natural ordering through Comparable. The one-argument form, sorted(Comparator<? super T>), uses a supplied comparison rule instead, working regardless of whether the elements implement Comparable at all. Interviewers commonly ask for both forms to check whether a candidate knows natural ordering is not the only option.
Q2. What does it mean for sorted() to be a stateful intermediate operation, and how is that different from filter() or map()?
A stateful operation needs information from more than one element to produce a result — sorted() cannot know where any single element belongs until it has seen every other element in the stream. filter() and map() are stateless: each one decides what to do with an element the instant it arrives, with no dependency on any other element. This is why sorted() must buffer the entire stream internally before emitting anything, while filter() and map() can process one element at a time and pass it straight along the pipeline.
Q3. What happens if you call sorted() with no arguments on a type that doesn't implement Comparable?
It throws ClassCastException at the point elements are actually compared, since sorted() attempts to cast each element to Comparable and call compareTo() on it. This is a frequent surprise with Java records specifically, since a record does not automatically implement Comparable just because it has fields — supplying an explicit Comparator avoids the problem entirely, regardless of whether the type implements Comparable.
Q4. Does combining sorted() with limit() let the JVM skip sorting elements beyond the limit?
No. sorted() is stateful and must process and order the entire stream before limit() gets a chance to run — there is no optimization that lets the combination skip sorting elements that end up outside the final limit. This is a favorite product-based interview question specifically because it separates candidates who understand sorted()'s actual cost from those who assume chaining limit() afterward makes it cheap.
Q5. Why can't sorted() be used directly on an infinite stream?
Because sorted() needs to see every element before it can determine the correct position of any single one, and an infinite stream, by definition, never finishes producing elements. Calling sorted() on an unbounded stream like Stream.iterate(1, n -> n + 1) without bounding it first with limit() beforehand leaves the pipeline waiting indefinitely, since sorted() never reaches the point where it has "everything" it needs.
Q6. How would you sort a stream of objects by one field descending and break ties with a second field ascending?
Chain Comparator.comparing() (or a primitive-specialized variant like comparingInt) with .reversed() for the primary descending field, then call .thenComparing() with the second field for the tie-break, exactly as the fantasy sports leaderboard example does: Comparator.comparingInt(Participant::points).reversed().thenComparing(Participant::submissionOrder). thenComparing only applies its rule when the primary comparator considers two elements equal.
FAQs
Is sorted() an intermediate or terminal operation?
Intermediate. It returns a new Stream, not a final result, and like every intermediate operation it does nothing on its own until a terminal operation such as collect or forEach actually runs the pipeline.
Does sorted() modify the original list or array the stream was built from?
No. sorted() produces a new, ordered stream and never touches its source — this is what allows it to work even on an immutable list built with List.of(), unlike Collections.sort(), which sorts a mutable list in place.
What is the difference between sorted() and Collections.sort()?
Collections.sort() mutates a List directly, in place, and requires that list to be mutable. sorted() is a stream operation that produces a new, ordered stream without touching the original source at all, fitting naturally into a larger pipeline rather than standing alone as a separate imperative step.
Can sorted() be used on a primitive stream like IntStream?
Yes. IntStream, LongStream, and DoubleStream each provide a no-argument sorted() that sorts in natural numeric ascending order directly, without needing a Comparator at all, since primitive numeric ordering is unambiguous.
Is the sort performed by sorted() stable?
Yes. Stream.sorted() is documented to be a stable sort, meaning elements that compare as equal keep their original relative order from the source stream — this matters whenever a secondary, unstated ordering (like insertion order) should be preserved among elements a Comparator treats as equivalent.
Does sorted() work correctly with a parallel stream?
Yes, the final sorted result is identical whether the stream is sequential or parallel — the parallel implementation still produces a fully and correctly ordered result. What differs is only the internal mechanics of how the sort is computed, not the correctness of the outcome.
Can I sort in descending order without writing Comparator.reversed()?
Yes, Comparator.reverseOrder() provides a ready-made descending natural-order comparator directly, useful when no custom field-based comparator is involved. When a field-based comparator is already being built with Comparator.comparing(), calling .reversed() on it is the more common way to flip that specific rule around.
Summary
sorted() orders a stream's elements either by their natural ordering or by a Comparator built with comparing, thenComparing, and reversed, and it does that work upfront, before anything downstream ever sees an element. Its defining trait is being stateful — it needs the whole stream before it can produce anything, which is exactly why it cannot run on an infinite source and why pairing it with limit() never makes the sort itself any cheaper.
The two things worth carrying forward are building sort rules with chained comparators instead of hand-written compareTo() logic, the way the leaderboard's tie-break rule does, and remembering that sorted() always processes the full stream regardless of how small the final result ends up being. distinct() is the next stateful operation worth understanding with the same mental model in mind.
What to Read Next
Learn how to remove duplicate elements from a Stream.