skip to content

What do ListCrudRepository and ListPagingAndSortingRepository add over their base interfaces, and why were they introduced?

level: middleimportance: should knowfreq 45%

answer

  1. Spring Data 3.0 addition
  2. List instead of Iterable
  3. covariant return-type override only
  4. JpaRepository now extends the List variants
  5. ListPagingAndSorting overrides findAll(Sort) only

basics

~10 s

They were added in Spring Data 3.0. ListCrudRepository is like CrudRepository but findAll, findAllById and saveAll return List<T> instead of Iterable<T>. ListPagingAndSortingRepository makes findAll(Sort) return List<T>.

solid answer

~40 s

Spring Data 3.0 (2022) introduced List-returning variants because Iterable<T> is awkward to work with — you can't call .size(), .stream(), or index into it directly. ListCrudRepository<T, ID> extends CrudRepository and overrides the collection-returning methods so findAll(), findAllById(...) and saveAll(...) return List<T> instead of Iterable<T>. ListPagingAndSortingRepository<T, ID> extends PagingAndSortingRepository and overrides findAll(Sort) to return List<T>. The overrides are purely on the return type; behavior is identical, and covariant return types make this backward compatible. Notably, JpaRepository in 3.0 was re-parented to extend ListCrudRepository and ListPagingAndSortingRepository, so JPA users already get List returns without opting in explicitly. You'd extend the List variants directly when you want ergonomic List returns but don't want the full JPA (or other store-specific) surface.

code

java · 12 lines
java
// Ergonomic List returns without pulling in the full JpaRepository surface:
public interface ProductRepository extends ListCrudRepository<Product, Long> {
    List<Product> findByActiveTrue();
}

// Now callers get List directly:
List<Product> all = repo.findAll();      // List, so .size()/.stream() work
int n = all.size();
List<Product> saved = repo.saveAll(batch); // List<Product>

// With plain CrudRepository you'd have gotten Iterable<Product> and needed
// StreamSupport/manual conversion to call size()/stream().

go deeper

for a junior

Know that List variants return List<T> instead of Iterable<T> for findAll — easier to use.

for a middle

Explain covariant return-type overrides, which methods change, and that they arrived in Spring Data 3.0.

for a senior

Discuss the JpaRepository re-parenting and choosing List variants for a leaner, store-portable surface.

for a principal

Weigh interface-surface minimization: List variants vs JpaRepository's broader (flush/batch) API when defining team repository conventions.

## The problem the List variants solve Historically `CrudRepository.findAll()` returns **`Iterable<T>`**. `Iterable` is minimal — it only guarantees you can loop with a for-each. You **cannot** call `.size()`, `.get(i)`, `.isEmpty()`, or `.stream()` on it directly, so callers constantly wrote boilerplate to convert it to a `List` (e.g. via `StreamSupport.stream(it.spliterator(), false).toList()`). Iterable was chosen as the base return type because it's the lowest common denominator — some stores can stream lazily rather than materialize everything. ## What Spring Data 3.0 added **`ListCrudRepository<T, ID>`** `extends CrudRepository<T, ID>` and **overrides** the collection-returning methods with a narrower (covariant) return type: - `List<T> findAll()` (was `Iterable<T>`) - `List<T> findAllById(Iterable<ID> ids)` - `<S extends T> List<S> saveAll(Iterable<S> entities)` Scalar methods (`findById`, `save`, `count`, `deleteById`, …) are unchanged — they're inherited as-is. **`ListPagingAndSortingRepository<T, ID>`** `extends PagingAndSortingRepository<T, ID>` and overrides: - `List<T> findAll(Sort sort)` (was `Iterable<T>`) Note it does **not** change `findAll(Pageable)` — that already returns `Page<T>`, which is a rich type, so there's nothing to improve. ## Why this is safe (covariant returns) Overriding a method to return a **subtype** (`List` is a subtype of `Iterable`) is legal in Java (covariant return types) and fully backward compatible — existing code expecting `Iterable` still compiles against a `List` result. So the List variants are drop-in improvements. ## The JpaRepository re-parenting A key consequence: in Spring Data 3.0, **`JpaRepository<T, ID>` was changed to extend `ListCrudRepository<T, ID>` and `ListPagingAndSortingRepository<T, ID>`** (plus `QueryByExampleExecutor`). Before 3.0 it extended `PagingAndSortingRepository` and `CrudRepository`. `JpaRepository` had *already* overridden `findAll()` to return `List` historically, but the re-parenting made the List behavior part of the shared Commons hierarchy. So most JPA users already enjoy `List` returns without ever naming `ListCrudRepository`. ## When to use the List variants directly - You want ergonomic `List` returns **but not** the full store-specific surface (`JpaRepository` also drags in `flush`, `saveAndFlush`, `getReferenceById`, `deleteAllInBatch`, etc.). - You want a store-portable repository (Commons-only types) that still returns `List`. ## Gotchas - These interfaces live in **Spring Data Commons** (`org.springframework.data.repository`), available to all stores, not just JPA. - They add **no new methods** beyond return-type overrides — don't claim they add pagination or new queries. - They exist only from **Spring Data 3.0 / Spring Boot 3.0** onward; older projects won't have them.

  • Does JpaRepository require you to also extend ListCrudRepository to get List returns?
    No. Since Spring Data 3.0, JpaRepository already extends ListCrudRepository and ListPagingAndSortingRepository, so List returns come for free with JpaRepository.
  • How is overriding findAll() to return List instead of Iterable backward compatible?
    Java allows covariant return types — narrowing a return type to a subtype (List is a subtype of Iterable) is a legal override, and existing callers expecting Iterable still work.

saying these in an interview costs you the question

  • Claiming the List variants add pagination or new query methods
  • Saying they existed before Spring Data 3.0
  • Thinking they change scalar methods like save/findById

context