Question
CrudRepository vs JpaRepository in Spring Data JPA
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:
CrudRepositorygives you the basic Create, Read, Update, Delete operations.JpaRepositoryincludes everything fromCrudRepositoryand 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 fromCrudRepository JpaRepositoryis a more specialized interface for JPACrudRepositoryis 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 justIterable<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
Listresults directly - more advanced JPA behavior
Why this matters
Choosing the interface communicates intent.
- Use
CrudRepositorywhen you want a small, focused API - Use
JpaRepositorywhen 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:
CrudRepositoryis a basic toolbox with the essential tools: save, find, delete, count.JpaRepositoryis 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.
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
-
new User(1L, "Ava")- A new Java object is created in memory.
-
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.
-
userRepository.findAll()- A query is executed to fetch all
Userrows. - Because this is
JpaRepository, the result is returned as aList<User>.
- A query is executed to fetch all
-
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 worksfindById()still worksdeleteById()still worksfindAll()returnsIterable<User>instead ofList<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
Listresults directly - Larger enterprise apps where repository convenience methods save development time
Example scenarios
- User management system:
JpaRepositoryis convenient because you may need paging, sorting, and bulk operations. - Simple key-value style entity storage:
CrudRepositorymay be enough. - Order processing app:
JpaRepositorycan help when flushing changes before later transaction logic depends on them.
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
Listfor 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()returnsIterable<User>JpaRepository.findAll()returnsList<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
JpaRepositoryif you needflush()or batch operations - Or keep
CrudRepositoryif 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
| Interface | Purpose | Key return types | Extra features | Best when |
|---|---|---|---|---|
CrudRepository | Basic CRUD operations | Often Iterable<T> | Save, find, delete, count, exists | You want a minimal repository API |
PagingAndSortingRepository | CRUD plus paging and sorting | Iterable<T> | findAll(Sort sort), pagination support | You need paging but not necessarily full JPA helpers |
JpaRepository | Full JPA-oriented repository | Often List<T> | flush(), saveAndFlush(), batch deletes, JPA convenience methods | You are using JPA and want more built-in power |
CrudRepository vs JpaRepository
| Feature | CrudRepository | JpaRepository |
|---|---|---|
| Basic CRUD methods | Yes | Yes |
findAll() returns List<T> | No | Yes |
| Pagination and sorting | No, not directly | Yes |
flush() | No | Yes |
saveAndFlush() | No | Yes |
| Batch delete methods | No | Yes |
| Best for minimal API | Yes | Sometimes |
| Best for JPA-heavy apps | Limited | Yes |
Which one should you choose?
- Choose
CrudRepositorywhen basic CRUD is all you need. - Choose
JpaRepositorywhen you want JPA convenience methods and richer defaults. - In many Spring Data JPA apps,
JpaRepositoryis the practical default.
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()returnsIterable<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? UseJpaRepository
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.
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
Bookwithidandtitlefields. - Define a repository interface for
Bookand test basic save and find operations. - Show one version using
CrudRepositoryand one version usingJpaRepository. - Demonstrate at least one method that exists in
JpaRepositorybut not inCrudRepository.
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.