Minimal reproducer: sortBy(…) is ignored by fluent Specification queries (JpaSpecificationExecutor.findBy(…)) in two situations:
slice(…)with aPageRequestthat carries no sortpage(…)/slice(…)withPageable.unpaged()
In both cases the executed SQL has no ORDER BY at all, while page(…) with the same unsorted PageRequest applies the sort correctly.
- Spring Boot 4.1.0 / Spring Data JPA 4.1.0 / Java 21
- Embedded in-memory H2 — no external setup required (the behavior is database-independent; it reproduces on PostgreSQL 17 as well)
./mvnw spring-boot:run # Windows: .\mvnw.cmd spring-boot:runThe application executes both cases on startup, prints the results, and exits.
The demo seeds 15 PAID orders whose createdAt values do not follow insertion order, then runs both cases. org.hibernate.SQL=debug is enabled, so the missing ORDER BY is directly visible in the log.
Slice<CustomerOrder> slice = orders.findBy(
OrderSpecs.statusIs(Status.PAID),
q -> q.sortBy(Sort.by(DESC, "createdAt")).slice(PageRequest.of(0, 5)));| result | executed SQL | |
|---|---|---|
| expected | ORD-013, ORD-007, … (createdAt desc) | … order by co1_0.created_at desc offset ? rows fetch first ? rows only |
| actual | ORD-001 … ORD-005 (insertion order) | … offset ? rows fetch first ? rows only — no order by |
page(…) with the exact same arguments sorts correctly, so switching pagination from Page to Slice (e.g. for infinite scrolling) silently changes the row order and causes duplicated/missing items between scroll requests.
Page<CustomerOrder> page = orders.findBy(
OrderSpecs.amountAtLeast(20_000),
q -> q.sortBy(Sort.by(DESC, "totalAmount")).page(Pageable.unpaged()));| result | |
|---|---|
| expected | ORD-015, ORD-014, … (totalAmount desc) |
| actual | ORD-011, ORD-012, … (insertion order) — the SQL has no order by |
FetchableFluentQueryBySpecification uses pageable.getSort() instead of pageable.getSortOr(this.sort):
readSlice(…)builds its query withcreateSortedAndProjectedQuery(pageable.getSort()), dropping thesortBy(…)sort (case 1), whilereadPage(…)right below correctly usesgetSortOr(this.sort).- The unpaged branches of
slice(Pageable),page(Pageable), andpage(Pageable, countSpec)callall(pageable.getSort())(case 2). ThegetSortOr(this.sort)calls introduced by the GH-3762 fix (commit16495066f) are no longer present onmain.
The Querydsl twin, FetchableFluentQueryByPredicate, uses pageable.getSortOr(this.sort) in all of these paths and behaves as expected (verified by QuerydslJpaPredicateExecutorUnitTests.findByFluentPredicateSlice, GH-3764).