skip to content

How do compareBy/thenBy relate to java.util.Comparator under the hood, and how would you adapt or reuse a Comparator across Kotlin's sortedWith, maxWith, and Java APIs?

level: principalimportance: nice to knowfreq 30%

answer

  1. kotlin.Comparator == java.util.Comparator on JVM (no adapter)
  2. compareBy/thenBy return java.util.Comparator
  3. Can call Java .reversed()/.thenComparing() on Kotlin-built comparators
  4. naturalOrder() turns a Comparable type into a Comparator
  5. Share one comparator across sortedWith, maxWith, PriorityQueue, TreeMap, Stream

basics

~10 s

Kotlin's compareBy and thenBy actually return java.util.Comparator instances. So a comparator you build in Kotlin works directly with Java's Comparator methods and with Kotlin functions like sortedWith, maxWith, and TreeMap.

solid answer

~40 s

Kotlin does not have its own Comparator type — kotlin.Comparator is a typealias-like mapping onto java.util.Comparator<T> on the JVM. So compareBy, compareByDescending, thenBy, thenByDescending, nullsFirst, naturalOrder all produce a java.util.Comparator. That means you can freely interop: pass a Kotlin-built comparator to Java's Collections.sort, Stream.sorted, or PriorityQueue(comparator); and call Java's default methods like .reversed(), .thenComparing(...), or Comparator.comparing(...) and feed the result to Kotlin's sortedWith/maxWith/minWith/sortedSetOf. You can wrap a lambda with Comparator { a, b -> ... } or the kotlin.comparisons Comparator(...) factory. A Comparable type can be turned into a Comparator via naturalOrder(), and any Comparator can be applied to non-Comparable types. Prefer Kotlin's compareBy chain for readability but reach for Comparator.comparing(...).thenComparing(...) when staying in a Java-heavy codebase.

code

kotlin · 8 lines
kotlin
data class Person(val lastName: String, val firstName: String, val age: Int)

val cmp = compareBy<Person> { it.lastName }
    .thenComparing(Person::firstName)   // Java default method
    .reversed()                          // Java default method

val list = people.sortedWith(cmp)        // Kotlin
val heap = java.util.PriorityQueue(cmp)  // Java, same comparator object

go deeper

for a junior

Knows a comparator built in Kotlin can be passed to sorting functions.

for a middle

Understands compareBy/thenBy build reusable comparators usable with sortedWith and maxWith.

for a senior

Knows kotlin.Comparator is java.util.Comparator and mixes Kotlin builders with Java default methods and APIs freely.

for a principal

Designs ordering as a single shared, stateless comparator reused across collections, heaps, trees, and stream pipelines, choosing Kotlin vs Java idioms per codebase.

## One Comparator type on the JVM On Kotlin/JVM, `kotlin.Comparator<T>` maps to `java.util.Comparator<T>` — there is effectively **one** interface. Consequence: everything is interoperable in both directions, no adapters needed. ## What the Kotlin builders return All of these return a `java.util.Comparator`: - `compareBy { }`, `compareByDescending { }` - `.thenBy { }`, `.thenByDescending { }`, `.thenComparator { }` - `naturalOrder<T>()`, `reverseOrder<T>()` - `nullsFirst(...)`, `nullsLast(...)` - the `Comparator { a, b -> ... }` SAM factory and the `kotlin.comparisons.Comparator(...)` helper So a Kotlin-built comparator can call Java default methods: ```kotlin val c = compareBy<Person> { it.lastName } .reversed() // java.util.Comparator.reversed .thenComparing(Person::firstName) // java.util.Comparator.thenComparing ``` ## Feeding comparators everywhere ```kotlin val cmp = compareBy<Person> { it.age } people.sortedWith(cmp) // Kotlin: returns new List people.maxWith(cmp) // Kotlin: largest by cmp java.util.Collections.sort(mutablePeople, cmp) // Java API val pq = java.util.PriorityQueue(cmp) // Java heap ordered by cmp val tree = java.util.TreeMap<Person, V>(cmp) // Java sorted map people.stream().sorted(cmp).toList() // Java Stream ``` The **same object** works in Kotlin (`sortedWith`, `maxWith`, `minWith`, `sortedSetOf(cmp, ...)`) and Java APIs. ## Going from Comparable to Comparator and back - Have a `Comparable` type but need a `Comparator`? Use `naturalOrder<T>()` (or `compareBy { it }`). - Have a `Comparator` but a function wants `Comparable`? You can't make an arbitrary type Comparable retroactively, but you can always supply the Comparator via the `*With` variants (`sortedWith`, `maxWith`) — that's the whole point of the Comparator overloads. ## Java's Comparator.comparing vs Kotlin's compareBy They're equivalent in power: ```kotlin // Java style Comparator.comparing(Person::lastName).thenComparing(Person::firstName) // Kotlin style compareBy(Person::lastName).thenBy(Person::firstName) ``` Kotlin's chain is usually cleaner and handles primitives without boxing helpers like `comparingInt`. But in mixed code, mixing them is fine because the result types coincide. ## SAM conversion and lambdas Because `Comparator` is a functional (SAM) interface, you can write `Comparator { a, b -> a.size - b.size }`, but prefer `compareBy { it.size }` to avoid manual subtraction/overflow and to keep null-safety. ## Practical guidance - Build with `compareBy`/`thenBy` for readability and overflow safety. - Drop into Java default methods (`reversed`, `thenComparing`) when convenient — no conversion cost. - Reuse one comparator object for sorting, heaps, trees, and stream pipelines; it is stateless and thread-safe to share.

  • Do you need an adapter to pass a Kotlin compareBy result to java.util.Collections.sort?
    No. kotlin.Comparator is java.util.Comparator on the JVM, so the same instance is accepted directly.
  • When would you prefer Comparator.comparingInt over compareBy?
    In Java-interop heavy code to avoid boxing, but Kotlin's compareBy already handles primitive selectors without boxing concerns, so compareBy is usually fine.

saying these in an interview costs you the question

  • Thinking Kotlin has a separate Comparator type needing conversion to Java's
  • Writing a SAM Comparator with a - b instead of compareBy (overflow risk)
  • Believing you must rebuild a comparator to use it in a PriorityQueue or TreeMap
  • Assuming .thenComparing isn't available on Kotlin-built comparators
  • Claiming a comparator is stateful/unsafe to share across threads

context