Question

CrudRepository vs JpaRepository in Spring Data JPA

javaspringspring-data-jpaspring-repositories

Question

In Spring Data JPA, what is the difference between the CrudRepository and JpaRepository interfaces?

They often seem to be used interchangeably in examples, so it can be unclear what each one is for.

Why would you choose one over the other, and what additional features does JpaRepository provide compared with CrudRepository?

Short Answer

By the end of this page, you will understand how CrudRepository and JpaRepository are related, what methods each one provides, and when to choose the simpler interface versus the more feature-rich one in a Spring Data JPA application.

Concept

CrudRepository and JpaRepository are both repository interfaces from the Spring Data ecosystem, but they serve slightly different levels of functionality.

At a high level:

  • CrudRepository gives you the basic Create, Read, Update, Delete operations.
  • JpaRepository includes everything from CrudRepository and adds extra features that are useful specifically for JPA-based applications.

The inheritance relationship

JpaRepository is not completely separate. It builds on smaller repository interfaces.

The hierarchy is roughly:

Repository
  └── CrudRepository
        └── PagingAndSortingRepository
              └── JpaRepository

That means:

  • If you use JpaRepository, you also get all methods from CrudRepository
  • JpaRepository is a more specialized interface for JPA
  • CrudRepository is the minimal common choice when you only need basic data access

What CrudRepository provides

CrudRepository<T, ID> includes common methods such as:

<S extends T> S save(S entity);
Optional<T> findById(ID id);
Iterable<T> findAll();
void deleteById(ID id);
long count();
boolean existsById(ID id);

This is enough for many small applications or simple services.

What JpaRepository adds

JpaRepository<T, ID> adds JPA-specific convenience methods such as:

  • List<T> findAll() instead of just Iterable<T>
  • List<T> findAllById(Iterable<ID> ids)
  • flush()
  • saveAndFlush()
  • deleteAllInBatch()
  • deleteAllByIdInBatch()
  • getReferenceById() in newer versions

These methods are useful when working with:

  • batching n- flushing the persistence context
  • getting List results directly
  • more advanced JPA behavior

Why this matters

Choosing the interface communicates intent.

  • Use CrudRepository when you want a small, focused API
  • Use JpaRepository when you know your project uses JPA and you want richer features

In real projects, many developers choose JpaRepository by default because it is convenient and includes pagination, sorting, and JPA-specific operations. But if your goal is to expose only the smallest set of repository operations, CrudRepository can make the code feel simpler.

Mental Model

Think of these interfaces like toolboxes:

  • CrudRepository is a basic toolbox with the essential tools: save, find, delete, count.
  • JpaRepository is a larger toolbox that includes the basic tools plus advanced ones for JPA-specific tasks.

If all you need is a screwdriver and a hammer, the small toolbox is enough. If you also need a wrench set, measuring tape, and specialized tools, the larger toolbox saves time.

Both can do the basic job. The difference is how many built-in tools you want available.

Take Quiz

Syntax and Examples

A repository interface in Spring Data JPA is usually declared by extending one of the Spring Data repository interfaces.

Using CrudRepository

import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User, Long> {
}

This gives you basic methods like:

userRepository.save(user);
userRepository.findById(1L);
userRepository.findAll();
userRepository.deleteById(1L);

Using JpaRepository

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

This gives you all CRUD methods plus JPA-specific ones like:

userRepository.saveAndFlush(user);
userRepository.findAll();      // returns List<User>
userRepository.flush();
userRepository.deleteAllInBatch();

Example entity

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    private String name;

    public User() {}

    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Example custom query method

Both interfaces also support derived query methods:

public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByName(String name);
}

The big difference is not custom query support. The difference is the set of built-in methods you inherit.

Step by Step Execution

Consider this repository:

public interface UserRepository extends JpaRepository<User, Long> {
}

And this service code:

User user = new User(1L, "Ava");
userRepository.save(user);
List<User> users = userRepository.findAll();
userRepository.flush();

What happens step by step

  1. new User(1L, "Ava")

    • A new Java object is created in memory.
  2. userRepository.save(user)

    • Spring Data delegates the save operation to the JPA provider.
    • If the entity is new, it will be inserted.
    • If it already exists, it may be updated.
  3. userRepository.findAll()

    • A query is executed to fetch all User rows.
    • Because this is JpaRepository, the result is returned as a List<User>.
  4. userRepository.flush()

    • Pending changes in the persistence context are synchronized with the database immediately.
    • This is useful when you need SQL execution to happen before the transaction ends.

If you used CrudRepository instead

public interface UserRepository extends CrudRepository<User, Long> {
}

Then:

  • save() still works
  • findById() still works
  • deleteById() still works
  • findAll() returns Iterable<User> instead of List<User>
  • methods like flush() are not available

Real World Use Cases

Here are common situations where these interfaces appear in real applications.

When CrudRepository is enough

  • A small internal app with simple create, read, update, delete screens
  • A microservice that only stores and fetches records by ID
  • A codebase where the team wants to expose only minimal repository operations
  • Teaching or learning examples focused only on basic persistence

When JpaRepository is more useful

  • Applications that need pagination and sorting often
  • Services that work with JPA-specific behavior like flushing or batch deletes
  • Admin dashboards that list many records and need List results directly
  • Larger enterprise apps where repository convenience methods save development time

Example scenarios

  • User management system: JpaRepository is convenient because you may need paging, sorting, and bulk operations.
  • Simple key-value style entity storage: CrudRepository may be enough.
  • Order processing app: JpaRepository can help when flushing changes before later transaction logic depends on them.
Take Quiz

Real Codebase Usage

In real projects, developers often choose a repository interface based on how much behavior they want available by default.

Common pattern: use JpaRepository as the default

Many Spring Boot applications use:

public interface OrderRepository extends JpaRepository<Order, Long> {
}

Why?

  • It is convenient
  • It returns List for common methods
  • It supports pagination and sorting through inherited interfaces
  • It exposes JPA-specific helper methods

Common pattern: use a smaller interface intentionally

Some teams prefer:

public interface ProductRepository extends CrudRepository<Product, Long> {
}

This can help:

  • keep APIs smaller
  • reduce accidental misuse of advanced methods
  • make the repository contract more focused

Validation and service-layer usage

Repositories are usually called from a service layer:

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(User user) {
        if (user.getName() == null || user.getName().isBlank()) {
            throw new IllegalArgumentException("Name is required");
        }
        return userRepository.save(user);
    }
}

Guard clauses and early returns

A common pattern is validating input before repository access:

public Optional<User> getUser(Long id) {
    if (id == null) {
        return Optional.empty();
    }
    return userRepository.findById(id);
}

Batch and flush usage

In more advanced JPA codebases, methods like these matter:

userRepository.saveAndFlush(user);
userRepository.deleteAllInBatch();

These are reasons teams choose JpaRepository over CrudRepository.

Common Mistakes

1. Thinking they are unrelated interfaces

A common misunderstanding is assuming you must choose between two completely separate repository systems.

In reality, JpaRepository extends the simpler repository interfaces.

2. Expecting CrudRepository.findAll() to return a List

Broken assumption:

CrudRepository<User, Long> repo = null;
List<User> users = repo.findAll(); // compile-time problem

Why it fails:

  • CrudRepository.findAll() returns Iterable<User>
  • JpaRepository.findAll() returns List<User>

Correct approach:

Iterable<User> users = repo.findAll();

Or use JpaRepository if a List is what you want.

3. Using JpaRepository methods when the repository only extends CrudRepository

Broken code:

public interface UserRepository extends CrudRepository<User, Long> {
}

userRepository.flush(); // not available

Fix:

  • Extend JpaRepository if you need flush() or batch operations
  • Or keep CrudRepository if you only need basic CRUD

4. Choosing JpaRepository without understanding why

It is common to copy examples and use JpaRepository everywhere. That is not always wrong, but you should know what it implies:

  • a larger API surface
  • JPA-specific methods available everywhere

5. Assuming the repository choice changes custom query method support

Both can support query methods like:

Optional<User> findByEmail(String email);

The difference is mainly in the inherited built-in methods, not whether derived query methods work.

6. Forgetting that flush() is JPA-specific

flush() does not just mean “save.” It means pending persistence context changes are pushed to the database immediately.

Use it only when you actually need that behavior.

Comparisons

Repository interface comparison

InterfacePurposeKey return typesExtra featuresBest when
CrudRepositoryBasic CRUD operationsOften Iterable<T>Save, find, delete, count, existsYou want a minimal repository API
PagingAndSortingRepositoryCRUD plus paging and sortingIterable<T>findAll(Sort sort), pagination supportYou need paging but not necessarily full JPA helpers
JpaRepositoryFull JPA-oriented repositoryOften List<T>flush(), saveAndFlush(), batch deletes, JPA convenience methodsYou are using JPA and want more built-in power

CrudRepository vs JpaRepository

FeatureCrudRepositoryJpaRepository
Basic CRUD methodsYesYes
findAll() returns List<T>NoYes
Pagination and sortingNo, not directlyYes
flush()NoYes
saveAndFlush()NoYes
Batch delete methodsNoYes
Best for minimal APIYesSometimes
Best for JPA-heavy appsLimitedYes

Which one should you choose?

  • Choose CrudRepository when basic CRUD is all you need.
  • Choose JpaRepository when you want JPA convenience methods and richer defaults.
  • In many Spring Data JPA apps, JpaRepository is the practical default.
Take Quiz

Cheat Sheet

Quick reference

CrudRepository

public interface UserRepository extends CrudRepository<User, Long> {
}

Common methods:

save(entity)
findById(id)
findAll()
deleteById(id)
count()
existsById(id)

Notes:

  • Minimal API
  • findAll() returns Iterable<T>
  • No flush() or batch JPA helpers

JpaRepository

public interface UserRepository extends JpaRepository<User, Long> {
}

Includes everything from CrudRepository plus:

findAll()            // returns List<T>
flush()
saveAndFlush(entity)
deleteAllInBatch()
deleteAllByIdInBatch(ids)
getReferenceById(id)

Notes:

  • JPA-specific convenience methods
  • Good default for many Spring Data JPA projects
  • Also includes paging and sorting support through inherited interfaces

Rule of thumb

  • Need only basic CRUD? Use CrudRepository
  • Need List, pagination, sorting, flush, or batch JPA methods? Use JpaRepository

Key inheritance chain

Repository
 -> CrudRepository
 -> PagingAndSortingRepository
 -> JpaRepository

FAQ

What is the main difference between CrudRepository and JpaRepository?

CrudRepository provides basic CRUD operations. JpaRepository includes those operations and adds JPA-specific features such as flush(), saveAndFlush(), batch deletes, and List-based find methods.

Is JpaRepository a subtype of CrudRepository?

Yes. JpaRepository extends other Spring Data repository interfaces, including CrudRepository, so it includes all CRUD methods.

Should I always use JpaRepository in Spring Boot?

Not always, but many projects do because it is convenient. If you only need simple CRUD and want a smaller API, CrudRepository is still a good choice.

Why does findAll() return different types?

With CrudRepository, findAll() returns Iterable<T>. With JpaRepository, it returns List<T>, which is often easier to use in application code.

Does CrudRepository support custom finder methods?

Yes. You can still define methods like findByEmail(String email) in a repository that extends CrudRepository.

When would I need flush()?

You need flush() when you want pending JPA changes written to the database immediately before the transaction completes.

Is PagingAndSortingRepository still relevant?

Yes. It sits between CrudRepository and JpaRepository in the hierarchy and adds pagination and sorting methods. JpaRepository already includes that functionality through inheritance.

Take Quiz

Mini Project

Description

Build a small Spring Data JPA repository layer for a Book entity to see the practical difference between CrudRepository and JpaRepository. This project demonstrates basic CRUD operations first, then shows how switching to JpaRepository gives you extra convenience like List results and flush() support.

Goal

Create a repository for books, save and read records, then compare what changes when the repository extends CrudRepository versus JpaRepository.

Requirements

  • Create a JPA entity named Book with id and title fields.
  • Define a repository interface for Book and test basic save and find operations.
  • Show one version using CrudRepository and one version using JpaRepository.
  • Demonstrate at least one method that exists in JpaRepository but not in CrudRepository.
Take Quiz

Keep learning

Related questions

View more