How do you build multi-field sorting with Sort, Sort.Order and Sort.Direction, including case-insensitive and null-handling options?
answer
- Sort.by(props) all ASC
- Order.asc/desc + .ignoreCase()/.nullsLast()
- Direction enum: ASC / DESC
- immutable — and()/ascending() return new
- first Order = primary key, rest = tie-breakers
basics
~10 sUse Sort.by(...) to name properties. For fine control, build Sort.Order objects — Order.asc("x"), Order.desc("y") — combine them with Sort.by(order1, order2), and refine each with .ignoreCase() or .nullsLast(). Direction is Sort.Direction.ASC or DESC.
solid answer
~40 sSort is an immutable value object describing ordering, independent of paging. The simplest form, Sort.by("lastName", "firstName"), sorts ascending by each property in order. For per-field control you compose Sort.Order instances: Sort.by(Sort.Order.asc("lastName"), Sort.Order.desc("createdAt")). Each Order carries a property, a Sort.Direction (ASC/DESC), an ignoreCase flag (Order.ignoreCase() adds LOWER(...) in SQL), and a NullHandling (nullsFirst()/nullsLast()/NATIVE). Sort.by(Direction.DESC, "age") applies one direction to many properties. Sort is immutable, so and(otherSort), ascending(), descending(), and withProperties() all return new instances. You either pass Sort directly to a repository method (findByActiveTrue(Sort)) or fold it into a PageRequest. Order matters: the first Order is the primary sort key, later Orders break ties. Property names must resolve to entity attribute paths (dot notation for nested), or the query fails.
code
java · 16 lines// Multi-field with per-field direction, case, and null handling
Sort sort = Sort.by(
Sort.Order.asc("lastName").ignoreCase(),
Sort.Order.desc("createdAt").nullsLast(),
Sort.Order.asc("id") // deterministic tie-breaker
);
List<User> users = userRepository.findByActiveTrue(sort);
// Parse a direction from a request param safely
Sort.Direction dir = Sort.Direction.fromOptionalString(paramDir)
.orElse(Sort.Direction.ASC);
Sort byAge = Sort.by(dir, "age");
// Combine into paging; sort is applied before LIMIT/OFFSET
PageRequest page = PageRequest.of(0, 25, sort);go deeper
Know Sort.by("field") and Sort.by(Direction.DESC, "field").
Compose Sort.Order with ignoreCase/null handling and understand immutability + tie-breaker ordering.
Address property-name validation, index impact of ignoreCase, dialect-dependent null handling, and sort-before-limit semantics.
Set conventions for safe sort-param whitelisting and predictable ordering across heterogeneous stores; understand where explicit null handling is unsupported.
**What Sort is.** `org.springframework.data.domain.Sort` is an immutable value object that expresses *how* results should be ordered, completely separate from *how many* to fetch (that's `Pageable`). You can use a `Sort` on its own — `List<User> findByActiveTrue(Sort sort)` — or embed it inside a `PageRequest`. **Sort.Direction.** An enum with two values, `ASC` and `DESC`. Helpers: `Direction.fromString("desc")` and `Direction.fromOptionalString(...)` parse strings safely (useful when a sort direction comes from a request parameter). `fromString` throws `IllegalArgumentException` on garbage; `fromOptionalString` returns an `Optional`. **Building a Sort — three styles:** 1. **Property names, all ascending:** `Sort.by("lastName", "firstName")`. 2. **One direction, many properties:** `Sort.by(Sort.Direction.DESC, "createdAt", "id")`. 3. **Per-field control with Sort.Order:** `Sort.by(Order.asc("lastName"), Order.desc("createdAt"))`. **Sort.Order** is the fine-grained unit. Each `Order` holds: - a **property** (entity attribute path; use dot notation for nested, e.g. `"address.city"`), - a **Sort.Direction**, - an **ignoreCase** flag, - a **NullHandling** value. Factory + fluent methods (all return a *new* Order because Order is immutable): `Order.asc(prop)`, `Order.desc(prop)`, `Order.by(prop)` (defaults ASC), `.with(Direction)`, `.ignoreCase()`, `.nullsFirst()`, `.nullsLast()`, `.nullsNative()`. **ignoreCase.** `Order.by("email").ignoreCase()` makes the store sort case-insensitively — in JPA/SQL this typically emits `ORDER BY LOWER(email)`. Note it only affects *ordering*, not filtering, and can defeat an index on the raw column. **NullHandling.** The enum `Sort.NullHandling` has `NATIVE` (let the database decide — Postgres puts NULLs last for ASC, Oracle first, etc.), `NULLS_FIRST`, and `NULLS_LAST`. Not every store honors explicit null handling; JPA support depends on the provider/dialect. **Composition and immutability.** `Sort` is immutable. Every mutator returns a new object: - `sort.and(otherSort)` — concatenates order lists (primary first). - `sort.ascending()` / `sort.descending()` — flips all directions. - `Sort.unsorted()` — an empty sort (no ORDER BY). Because the first `Order` is the **primary** key and subsequent ones are tie-breakers, order of composition matters. `Sort.by(Order.asc("lastName"), Order.asc("firstName"))` sorts by last name, then first name within equal last names. **How it combines with paging.** `PageRequest.of(page, size, sort)` or `existingPageRequest.withSort(sort)` merges ordering into a page request. The sort is applied *before* the LIMIT/OFFSET, so it determines which rows land on which page. **Gotchas.** - **Invalid property names throw at query time** (`PropertyReferenceException` for derived queries). A sort field coming from an untrusted request parameter is effectively an injection surface — validate/whitelist it, don't pass it straight into `Sort.by`. - `ignoreCase()` can silently bypass an index and slow large sorts. - Explicit NULLS_FIRST/LAST may be ignored by some JPA providers — verify with your database. - `Sort` alone (no `Pageable`) does not limit rows — it only orders them. **When to use.** Any query needing deterministic or user-chosen ordering; also essential as a tie-breaker to make pagination stable.
- A sort field comes from a query parameter. What's the risk and the fix?An arbitrary property name reaches Sort.by and can throw PropertyReferenceException or expose internal fields — an injection/enumeration surface. Whitelist allowed sort properties before constructing the Sort.
- What does Order.ignoreCase() actually change in SQL, and what's the cost?It sorts on LOWER(column) rather than the raw column, giving case-insensitive ordering — but that function wraps the column and can prevent the database from using a plain index, slowing large sorts.
saying these in an interview costs you the question
- Thinking Sort is mutable (that and()/ascending() modify in place)
- Believing Sort limits the number of rows
- Assuming NULLS_FIRST/LAST is honored by every database
- Passing an untrusted sort property straight into Sort.by