What is the difference between CrudRepository and JpaRepository

Luiz Gustavo De O. Costa
5 min readAug 21, 2021


By default when I need to write a code to communicate with Repository I thought to use CrudRepository, but the JpaRepository also is an option, so should I review my bias?

A380 Cockpit — From Andrés Dallimonti

1. Acronym

  • CRUD — Create Retrieve Update Delete operation
  • JPA — Java Persistence Api
  • API — Application Program Interface
  • IDE — Integrated Development Environment

2. Objective

Explain the difference between the interfaces CrudRepository and JpaRepository.

3. CrudRepository and JpaRepository

To explain the difference let’s see the diagrams of each one.

3.1. CrudRepository

From my point of view, the CrudRepository is the most used in tutorials/articles and does your work well.

CrudRepository diagram

3.2. JpaRepository

On the other hand, more complex, ie, allows you a fine grained control about your resource layer, so that’s great! 💪

In comparison with the CrudRepository this interface has important methods, such as findAll(Pageable) findAll(Sort) flush() and saveAndFlush(S). but pay attention when your code is calling flush, since the Flush cleans the Hibernate first level cache.

Ok, but in terms of code performance, is it relevant? Yes, thinking about the client, your client needs all clients at once? What kind of client are expected? Using JpaRepository and using Pageable is easy to achieve those requirements.

JpaRepository diagram

4. Hands on

To make this article more interesting, let’s see a project that implements both interfaces and creates one Entity to store a Post in the simplest way.

4.1. Project

Below there is a project snapshot, find below the picture what each part does

Project structure


1 — Post — Entity

2 — PostCrudRepository — Interface that extends the CrudRepository

3 — PostJpaRepository — Interface that extends the JpaRepository

4 — Menu — Class that serves the Menu options. This class communicates with the ui and the repos.

5 — AppMain — Startup (Boot) class marked as @SpringBootApplication

6 — application.yml — Application configuration file

7 — data.sql — used to populate the database on application startup

8 — schema.sql — used to create the database schema on application startup

9 — Test source folder

4.2. Class diagram

The diagram below shows the relationship between the entity and the repositories.

Class diagram

4.2.1. Post

This entity has few fields and a NamedQuery to find all posts by user and content text.

Notice that the NamedQuery is declared into PostCrudRepository. For convenience all customized methods are into PostCrudRepository, but could be into JpaCrudRepository as well.

package com.costa.luiz.twitter.model;

import lombok.*;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.time.ZonedDateTime;
import java.util.UUID;

@Table(name = "posts")
@NamedQuery(name = "Post.findAllByUserAndPost",
query = "select post from Post post where post.user = ?1 and post.content like CONCAT('%',?2,'%')")
public class Post {

@GeneratedValue(generator = "UUID")
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
private UUID id;
private String user;
private String content;
@Column(name = "created_at")
private ZonedDateTime createdAt;


4.2.2. PostCrudRepository

This interface has some customized queries, and follow the Spring pattern, in order to just declared the method and Spring build the query.

Just to given more examples were created two coded methods: findAllByUserAndPost and countPostContentWithContentLowerCase.

package com.costa.luiz.twitter.model;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.time.ZonedDateTime;
import java.util.List;

public interface PostCrudRepository extends CrudRepository<Post, String> {

List<Post> findAllByUser(String user);
List<Post> findAllByCreatedAtBetween(ZonedDateTime start, ZonedDateTime end);
List<Post> findAllByUserContains(String user);

List<Post> findAllByUserAndPost(String user, String post);

//Native Query
@Query(value = "SELECT count(1) FROM posts p WHERE lower(p.content) like CONCAT('%',?1,'%')", nativeQuery = true)
Long countPostContentWithContentLowerCase(String content);


4.2.3. PostJpaRepository

This is the interface PostJpaRepository, at this moment nothing customized here, but this interface is really powerful, let’s see this on the tests section.

package com.costa.luiz.twitter.model;

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

public interface PostJpaRepository extends JpaRepository<Post, String> {

5. Tests

First of all, it is good practice to define a scenario 😉. For all tests, will be used the following configuration: application.yml, schema.sql, and data.sql


5.1. CrudRepository

Important to remember ❕Since JpaRepository extends CrudRepository, all the methods/test are valid to both.

Retrieve all by user
Retrieve all by user contains word
Retrieve all — NamedQuery
Count all — Native query
Retrieve all between dates

5.2. JpaRepository

Now is where the difference emerges. The first one is the method deleteAllInBatch where Spring will delete all and clean the first level cache, paying attention to not have side effects in this kind of operation.

Delete all in batch

The second one is using the PageRequest object. Here was request the page 1 (index starts with 0), of size 2 and sorted by the field createdAt

Find all — using PageRequest

On the other hand is possible to sort the response sending some fields, in this case createdAt and user

Find all — using Sort

6. Code

The complete code is available in my GitHub.

7. Run locally

To run this project locally you need the following softwares:

  • Java 11
  • Maven

7.1. Compile

mvn clean package -DskipTests

Generate the package

7.2. Run the app

java -jar target/crud_vs_jpa-0.0.1-SNAPSHOT.jar

Application started and Menu

After the application has been started, using the keyboard you can: (exit) Exit, (0) create a random post, (1) count the posts and (2) read all posts

UI via Console

8. Conclusion

It’s clear to me the benefit to have a pageable query, delete by batch and so on. Apart from the pageable query, when the project needs to flush the data either by performance problems or by database issues is a good point. In my articles from now on, I’ll adopt the JpaRepository 🙌.

If you like of this kind of content, leave your claps 👏 👏 👏

9. References



Luiz Gustavo De O. Costa

Hey friend!! I’m Luiz Gustavo, a Java developer and I’m here to learn and write about Java, tests and good practices