Java lambdas ((arg1, arg2) -> code;) are somewhat weirdly type inferred. Inference goes inside out, then outside in, then inside out again.

The reason is that java's functional typing system is based off of so-called functional interfaces. You don't, in java, just have an expression of type (int, int) -> String.

That explains why in java you can write listOfStrings.sort(Comparator.comparing(x -> x.length()));. Because, think about that line for a moment: How in the blazes is java allowing you to write x.length() there? j.l.Object doesn't have length(). Why does x? We never mentioned what type x is.

That's because java resolves inside-out first: Okay, we have a lambda. It will represent some type but we don't yet know what. For now we know: It's a lambda, and, it takes 1 argument.

So then javac scans Comparator.comparing and finds a few overloads. It can immediately eliminate some. Eventually only one remains: It takes a Function<T, U>, and returns a Comparator<T>. Okay, so I guess for now we go with T t -> t.length(). That isn't much help. Yet. We keep going: That is passed to .sort, which requires a Comparator<E>. That just kicks the can down the road; replacing one typevar with another, not useful. Let's keep going: That sort method is called on a on a List<String>. aha! Only a Comparator<String> is valid there, so I guess E is string! So let's apply that all the way back down - we go with String t -> t.length(). And that, finally, 'fits'. So java compiles this whole thing interpreting x -> x.length() as 'a Function<String, Comparable-to-self>. And then re-scans that lambda to ensure it makes sense in that context. Only now could e.g. a typo in length() be found as 'hey, that's not a method strings have'.

The problem with your code snippet that doesn't compile is that the compiler is limited in how far it can take this inside-out-outside-in process. It has to go too far and gives up. (It's a lot more complicated than this, but, you need to fully grok large swathes of the JLS to go any further).

Add some casts to 'help' the compiler and it works again:

Comparator<Edge> myComparator = Comparator.comparing(e -> e.a.x()).thenComparing(Comparator.<Edge>comparing(e -> e.b.x()));

Or add a (Comparator<Edge>) cast. The reason your other snippet does work is the local variable types serve as that 'type hint'.

Answer from rzwitserloot on Stack Overflow
🌐
Medium
medium.com › @AlexanderObregon › javas-comparator-thencomparing-method-explained-988e8f926a64
Java’s Comparator.thenComparing() Explained | Medium
November 6, 2024 - When chaining multiple thenComparing() methods, readability can become a challenge, especially with more complex criteria. Breaking down sorting logic into helper methods is a great way to maintain clarity. Consider a scenario where we need to sort books by genre, then by author, and then by page count. Creating helper methods keeps the main sorting code uncluttered: Comparator<Book> byGenre = Comparator.comparing(Book::getGenre); Comparator<Book> byAuthor = Comparator.comparing(Book::getAuthor); Comparator<Book> byPageCount = Comparator.comparingInt(Book::getPageCount); Comparator<Book> bookComparator = byGenre.thenComparing(byAuthor).thenComparing(byPageCount); books.sort(bookComparator);
🌐
Baeldung
baeldung.com › home › java › guide to java comparator.comparing()
Guide to Java Comparator.comparing() | Baeldung
January 8, 2024 - @Test public void whenThenComparing_thenSortedByAgeName(){ Comparator<Employee> employee_Age_Name_Comparator = Comparator.comparing(Employee::getAge) .thenComparing(Employee::getName); Arrays.sort(someMoreEmployees, employee_Age_Name_Comparator); assertTrue(Arrays.equals(someMoreEmployees, sortedEmployeesByAgeName)); }
🌐
Oracle
docs.oracle.com › javase › 8 › docs › api › java › util › Comparator.html
Comparator (Java Platform SE 8 )
October 20, 2025 - Comparator<String> cmp = Comparator.comparingInt(String::length) .thenComparing(String.CASE_INSENSITIVE_ORDER);
🌐
Blogger
javarevisited.blogspot.com › 2021 › 09 › comparator-comparing-thenComparing-example-java-.html
Java 8 Comparator comparing() and thenComparing() Example - Tutorial
As the name suggests, you can use the thenComparing() method to chain comparators to compare objects on multiple fields and multiple orders.
🌐
How to do in Java
howtodoinjava.com › home › java sorting › java comparator thencomparing() example
Java Comparator thenComparing() Example - HowToDoInJava
August 30, 2022 - //first name comparator Comparator<Employee> compareByFirstName = Comparator.comparing( Employee::getFirstName ); //last name comparator Comparator<Employee> compareByLastName = Comparator.comparing( Employee::getLastName ); //Compare by first name and then last name (multiple fields) Comparator<Employee> compareByFullName = compareByFirstName.thenComparing(compareByLastName); //Use Comparator Collections.sort(employees, compareByFullName);
Top answer
1 of 1
5

Java lambdas ((arg1, arg2) -> code;) are somewhat weirdly type inferred. Inference goes inside out, then outside in, then inside out again.

The reason is that java's functional typing system is based off of so-called functional interfaces. You don't, in java, just have an expression of type (int, int) -> String.

That explains why in java you can write listOfStrings.sort(Comparator.comparing(x -> x.length()));. Because, think about that line for a moment: How in the blazes is java allowing you to write x.length() there? j.l.Object doesn't have length(). Why does x? We never mentioned what type x is.

That's because java resolves inside-out first: Okay, we have a lambda. It will represent some type but we don't yet know what. For now we know: It's a lambda, and, it takes 1 argument.

So then javac scans Comparator.comparing and finds a few overloads. It can immediately eliminate some. Eventually only one remains: It takes a Function<T, U>, and returns a Comparator<T>. Okay, so I guess for now we go with T t -> t.length(). That isn't much help. Yet. We keep going: That is passed to .sort, which requires a Comparator<E>. That just kicks the can down the road; replacing one typevar with another, not useful. Let's keep going: That sort method is called on a on a List<String>. aha! Only a Comparator<String> is valid there, so I guess E is string! So let's apply that all the way back down - we go with String t -> t.length(). And that, finally, 'fits'. So java compiles this whole thing interpreting x -> x.length() as 'a Function<String, Comparable-to-self>. And then re-scans that lambda to ensure it makes sense in that context. Only now could e.g. a typo in length() be found as 'hey, that's not a method strings have'.

The problem with your code snippet that doesn't compile is that the compiler is limited in how far it can take this inside-out-outside-in process. It has to go too far and gives up. (It's a lot more complicated than this, but, you need to fully grok large swathes of the JLS to go any further).

Add some casts to 'help' the compiler and it works again:

Comparator<Edge> myComparator = Comparator.comparing(e -> e.a.x()).thenComparing(Comparator.<Edge>comparing(e -> e.b.x()));

Or add a (Comparator<Edge>) cast. The reason your other snippet does work is the local variable types serve as that 'type hint'.

🌐
Tabnine
tabnine.com › home page › code › java › java.util.comparator
Java Examples & Tutorials of Comparator.thenComparing (java.util) | Tabnine
@Override public int compareTo(TotalRankSolverRankingWeight other) { return Comparator .comparingInt(TotalRankSolverRankingWeight::getBetterCount) .thenComparingInt(TotalRankSolverRankingWeight::getEqualCount) .thenComparingInt(TotalRankSolverRankingWeight::getLowerCount) .thenComparing(TotalRankSolverRankingWeight::getSolverBenchmarkResult, totalScoreSolverRankingComparator) // Tie-breaker .compare(this, other); }
Top answer
1 of 4
159

First, all the examples you say cause errors compile fine with the reference implementation (javac from JDK 8.) They also work fine in IntelliJ, so its quite possible the errors you're seeing are Eclipse-specific.

Your underlying question seems to be: "why does it stop working when I start chaining." The reason is, while lambda expressions and generic method invocations are poly expressions (their type is context-sensitive) when they appear as method parameters, when they appear instead as method receiver expressions, they are not.

When you say

Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));

there is enough type information to solve for both the type argument of comparing() and the argument type p1. The comparing() call gets its target type from the signature of Collections.sort, so it is known comparing() must return a Comparator<Song>, and therefore p1 must be Song.

But when you start chaining:

Collections.sort(playlist1,
                 comparing(p1 -> p1.getTitle())
                     .thenComparing(p1 -> p1.getDuration())
                     .thenComparing(p1 -> p1.getArtist()));

now we've got a problem. We know that the compound expression comparing(...).thenComparing(...) has a target type of Comparator<Song>, but because the receiver expression for the chain, comparing(p -> p.getTitle()), is a generic method call, and we can't infer its type parameters from its other arguments, we're kind of out of luck. Since we don't know the type of this expression, we don't know that it has a thenComparing method, etc.

There are several ways to fix this, all of which involve injecting more type information so that the initial object in the chain can be properly typed. Here they are, in rough order of decreasing desirability and increasing intrusiveness:

  • Use an exact method reference (one with no overloads), like Song::getTitle. This then gives enough type information to infer the type variables for the comparing() call, and therefore give it a type, and therefore continue down the chain.
  • Use an explicit lambda (as you did in your example).
  • Provide a type witness for the comparing() call: Comparator.<Song, String>comparing(...).
  • Provide an explicit target type with a cast, by casting the receiver expression to Comparator<Song>.
2 of 4
29

The problem is type inferencing. Without adding a (Song s) to the first comparison, comparator.comparing doesn't know the type of the input so it defaults to Object.

You can fix this problem 1 of 3 ways:

  1. Use the new Java 8 method reference syntax

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. Pull out each comparison step into a local reference

      Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist());
    
      Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration());
    
        Collections.sort(playlist,
                byName
                .thenComparing(byDuration)
                );
    

    EDIT

  3. Forcing the type returned by the Comparator (note you need both the input type and the comparison key type)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    

I think the "last" thenComparing syntax error is misleading you. It's actually a type problem with the whole chain, it's just the compiler only marking the end of the chain as a syntax error because that's when the final return type doesn't match I guess.

I'm not sure why List is doing a better inferencing job than Collection since it should do the same capture type but apparently not.

Find elsewhere
🌐
ConcretePage
concretepage.com › java › java-8 › java-comparator-thencomparing
Java Comparator.thenComparing
Comparator.thenComparing returns a lexicographic-order comparator that is called by a Comparator instance to sort the items using group of sort keys.
🌐
Medium
medium.com › @AlexanderObregon › javas-comparator-comparing-method-explained-342361288af6
Java’s Comparator.comparing() Method Explained | Medium
October 2, 2024 - Java’s Comparator.comparing() allows for sorting in reverse order using the reversed() method and combining multiple comparators using thenComparing().
🌐
Oracle
docs.oracle.com › javase › 10 › docs › api › java › util › Comparator.html
Comparator (Java SE 10 & JDK 10 )
Comparator<String> cmp = Comparator.comparingInt(String::length) .thenComparing(String.CASE_INSENSITIVE_ORDER);
🌐
Java67
java67.com › 2021 › 09 › java-comparator-multiple-fields-example.html
How to sort a List or Stream by Multiple Fields in Java? Comparator comparing() + thenComparing Example | Java67
In fact, comparing takes a Accepts ... that sort key using the specified Comparator. While thenComparing() only compares if this comparator thinks that two values are equals....
🌐
Oracle
docs.oracle.com › en › java › javase › 18 › docs › api › java.base › java › util › Comparator.html
Comparator (Java SE 18 & JDK 18)
August 18, 2022 - Comparator<String> cmp = Comparator.comparingInt(String::length) .thenComparing(String.CASE_INSENSITIVE_ORDER);
🌐
Coderanch
coderanch.com › t › 656713 › java › Type-inferencing-Lambdas-chained-Comparators
Type inferencing with Lambdas and chained Comparators (Java in General forum at Coderanch)
Hi all, I'm pondering the example below. It seems that when I chain Comparators using the 'thenComparing" type constructs, the type inferencing of the compiler dies. The example works perfectly, but notice that I have to cast all those 's' lambda parameters to Student.
🌐
Blogger
javarevisited.blogspot.com › 2021 › 09 › comparator-comparing-thenComparing-example-java-.html
Javarevisited: Java 8 Comparator comparing() and thenComparing() Example - Tutorial
As the name suggests, you can use the thenComparing() method to chain comparators to compare objects on multiple fields and multiple orders.
Top answer
1 of 3
1

Check out DiffBuilder. It can report which items are different.

DiffResult<Employee> diff = new DiffBuilder(emp1, emp2, ToStringStyle.SHORT_PREFIX_STYLE)
       .append("name", emp1.getName(), emp2.getName())
       .append("age", emp1.getAge(), emp2.getAge())
       .append("fulltime", emp1.getFulltime(), emp2.getFulltime())
       .build();

DiffResult has, among other things, a getDiffs() method that you can loop over to find what differs.

2 of 3
1

First of all, I don't think you understand how comparing().thenComparing() works. The result in your case won't be 0. The comparator compares the first statement, if they are equal it goes to the thenComparing() and so on. The result will be 0 if and only if all comparison()/thenComparing() are also equal. I actually wrote about this a few days ago: http://petrepopescu.tech/2021/01/simple-collection-manipulation-in-java-using-lambdas/

Anyway, back to your question, I think you are looking for an equal() where it also returns what fields were not equal. Here is a quick prototype.

public class Pair<T, U> {
    private Function<? super T, ? extends Comparable> function;
    private String fieldName;

    public Pair(Function<? super T, ? extends Comparable> function, String fieldName) {
        this.function = function;
        this.fieldName = fieldName;
    }
}

And the actual comparator:

public class MyComparator {
    List<Pair> whatToCompare = Arrays.asList(
            new Pair<Employee, String>(Employee::getName, "name"),
            new Pair<Employee, String>(Employee::getAge, "age"),
            new Pair<Employee, Double>(Employee::isFullTimeEmployee, "fullTimeEmployee")
    );
    public List<String> compare(Employee e1, Employee e2) {
        List<String> mismatch = new ArrayList<>();
        for(Pair pair:whatToCompare) {
            int result = Comparator.comparing(pair.getFunction()).compare(e1, e2);
            if (result != 0) {
                mismatch.add(pair.getFieldName());
            }
        }

        return mismatch;
    }
}
🌐
Javabrahman
javabrahman.com › java-8 › the-complete-java-8-comparator-tutorial-with-examples
The Complete Java 8 Comparator Tutorial with examples
This returns a Comparator instance with the first level sort based on Employee name as we saw in previous section. We append .thenComparing(Employee::getAge) to the Comparator instance returned using comparing() method, which adds a second level sort based on Employee's getAge() method.
🌐
Javaprogramto
javaprogramto.com › 2021 › 12 › java-comparator-thencomparing.html
Java 8 Comparator thenComparing()
thenComparing() method is used to sort the list of objects by multiple fields. An in-depth tutorial on Java 8 Comparator Interface with examples on multiple use cases.