Spring Boot, Spring Data JPA – Rest CRUD API example

In this tutorial, we’re gonna build a Spring Boot Rest CRUD API example with Maven that use Spring Data JPA to interact with MySQL/PostgreSQL database. You’ll know:

  • How to configure Spring Data, JPA, Hibernate to work with Database
  • How to define Data Models and Repository interfaces
  • Way to create Spring Rest Controller to process HTTP requests
  • Way to use Spring Data JPA to interact with PostgreSQL/MySQL Database
  • How to check CRUD operations with Postman

More Practice:
– Rest API without Controller: Spring Data REST example in Spring Boot
Validate Request Body in Spring Boot
Spring Boot WebFlux Rest API example
Spring Boot Thymeleaf CRUD example
Secure Spring Boot App with Spring Security & JWT Authentication
Spring Boot Rest XML example – Web service with XML Response
Spring Boot + GraphQL + MySQL example
Spring Boot Multipart File upload example
Spring Boot Pagination and Sorting example
– Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
– Caching: Spring Boot Redis Cache example
Associations:
Spring Boot One To One example with JPA, Hibernate
Spring Boot One To Many example with JPA, Hibernate
Spring Boot Many to Many example with JPA, Hibernate
Fullstack:
Vue + Spring Boot example
Angular 8 + Spring Boot example
Angular 10 + Spring Boot example
Angular 11 + Spring Boot example
Angular 12 + Spring Boot example
Angular 13 + Spring Boot example
Angular 14 + Spring Boot example
Angular 15 + Spring Boot example
Angular 16 + Spring Boot example
React + Spring Boot example
Exception Handling:
Spring Boot @ControllerAdvice & @ExceptionHandler example
@RestControllerAdvice example in Spring Boot
Testing:
Spring Boot Unit Test for JPA Repository
Spring Boot Unit Test for Rest Controller
Deployment:
Deploy Spring Boot App on AWS – Elastic Beanstalk
Docker Compose: Spring Boot and MySQL example
Docker Compose: Spring Boot and Postgres example

Overview of Spring Boot JPA Rest CRUD API example

We will build a Spring Boot JPA Rest CRUD API for a Tutorial application in that:

  • Each Tutorial has id, title, description, published status.
  • Apis help to create, retrieve, update, delete Tutorials.
  • Apis also support custom finder methods such as find by published status or by title.

These are APIs that we need to provide:

Methods Urls Actions
POST /api/tutorials create new Tutorial
GET /api/tutorials retrieve all Tutorials
GET /api/tutorials/:id retrieve a Tutorial by :id
PUT /api/tutorials/:id update a Tutorial by :id
DELETE /api/tutorials/:id delete a Tutorial by :id
DELETE /api/tutorials delete all Tutorials
GET /api/tutorials/published find all published Tutorials
GET /api/tutorials?title=[keyword] find all Tutorials which title contains keyword

– We make CRUD operations & finder methods with Spring Data JPA’s JpaRepository.
– The database could be PostgreSQL or MySQL depending on the way we configure project dependency & datasource.

If you want to use JdbcTemplate instead, kindly visit:
Spring Boot JdbcTemplate example with MySQL: CRUD App

Or Reactive API: Spring Boot R2DBC + MySQL example

Technology

  • Java 17 / 11 / 8
  • Spring Boot 3 / 2 (with Spring Web MVC, Spring Data JPA)
  • PostgreSQL/MySQL
  • Maven

Project Structure

spring-boot-data-jpa-crud-example-project-structure

Let me explain it briefly.

Tutorial data model class corresponds to entity and table tutorials.
TutorialRepository is an interface that extends JpaRepository for CRUD methods and custom finder methods. It will be autowired in TutorialController.
TutorialController is a RestController which has request mapping methods for RESTful requests such as: getAllTutorials, createTutorial, updateTutorial, deleteTutorial, findByPublished
– Configuration for Spring Datasource, JPA & Hibernate in application.properties.
pom.xml contains dependencies for Spring Boot and MySQL/PostgreSQL.

We can improve the example by adding Comments for each Tutorial. It is the One-to-Many Relationship and I write a tutorial for this at:
Spring Boot One To Many example with JPA, Hibernate

Or add Tags with Many-to-Many Relationship:
Spring Boot Many to Many example with JPA, Hibernate

Video

This is demo video and brief instruction of Spring Boot Rest Apis with Hibernate, MySQL example using Spring Data JPA:

Create & Setup Spring Boot project

Use Spring web tool or your development tool (Spring Tool Suite, Eclipse, Intellij) to create a Spring Boot project.

Then open pom.xml and add these dependencies:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

We also need to add one more dependency.
– If you want to use MySQL:
For Spring Boot 3:

<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
	<scope>runtime</scope>
</dependency>

For Spring Boot 2:

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

– or PostgreSQL:

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>

Configure Spring Datasource, JPA, Hibernate

Under src/main/resources folder, open application.properties and write these lines.

– For MySQL:

spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false
spring.datasource.username= root
spring.datasource.password= 123456

# for Spring Boot 2
# spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect

# for Spring Boot 3
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto= update

– For PostgreSQL:

spring.datasource.url= jdbc:postgresql://localhost:5432/testdb
spring.datasource.username= postgres
spring.datasource.password= 123

spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto= update
  • spring.datasource.username & spring.datasource.password properties are the same as your database installation.
  • Spring Boot uses Hibernate for JPA implementation, we configure MySQL5InnoDBDialect / MySQLDialect for MySQL or PostgreSQLDialect for PostgreSQL
  • spring.jpa.hibernate.ddl-auto is used for database initialization. We set the value to update value so that a table will be created in the database automatically corresponding to defined data model. Any change to the model will also trigger an update to the table. For production, this property should be validate.

Define Data Model

Our Data model is Tutorial with four fields: id, title, description, published.
In model package, we define Tutorial class.

model/Tutorial.java

package com.bezkoder.spring.datajpa.model;

import jakarta.persistence.*; // for Spring Boot 3
// import javax.persistence.*; // for Spring Boot 2

@Entity
@Table(name = "tutorials")
public class Tutorial {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;

	@Column(name = "title")
	private String title;

	@Column(name = "description")
	private String description;

	@Column(name = "published")
	private boolean published;

	public Tutorial() {

	}

	public Tutorial(String title, String description, boolean published) {
		this.title = title;
		this.description = description;
		this.published = published;
	}

	public long getId() {
		return id;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public boolean isPublished() {
		return published;
	}

	public void setPublished(boolean isPublished) {
		this.published = isPublished;
	}

	@Override
	public String toString() {
		return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
	}
}

@Entity annotation indicates that the class is a persistent Java class.
@Table annotation provides the table that maps this entity.
@Id annotation is for the primary key.
@GeneratedValue annotation is used to define generation strategy for the primary key. GenerationType.AUTO means Auto Increment field.
@Column annotation is used to define the column in database that maps annotated field.

Create Repository Interface

Let’s create a repository to interact with Tutorials from the database.
In repository package, create TutorialRepository interface that extends JpaRepository.

repository/TutorialRepository.java

package com.bezkoder.spring.datajpa.repository;

import java.util.List;

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

import com.bezkoder.spring.datajpa.model.Tutorial;

public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
  List<Tutorial> findByPublished(boolean published);
  List<Tutorial> findByTitleContaining(String title);
}

Now we can use JpaRepository’s methods: save(), findOne(), findById(), findAll(), count(), delete(), deleteById()… without implementing these methods.

We also define custom finder methods:
findByPublished(): returns all Tutorials with published having value as input published.
findByTitleContaining(): returns all Tutorials which title contains input title.

The implementation is plugged in by Spring Data JPA automatically.

You can modify this Repository:
– to work with Pagination, the instruction can be found at:
Spring Boot Pagination & Filter example | Spring JPA, Pageable
– to sort/order by multiple fields with the tutorial:
Spring Data JPA Sort/Order by multiple Columns | Spring Boot

More Derived queries at:
JPA Repository query example in Spring Boot

Custom query with @Query annotation:
Spring JPA @Query example: Custom query in Spring Boot

You also find way to write Unit Test for this JPA Repository at:
Spring Boot Unit Test for JPA Repository with @DataJpaTest

Create Spring Rest APIs Controller

Finally, we create a controller that provides APIs for creating, retrieving, updating, deleting and finding Tutorials.

controller/TutorialController.java

package com.bezkoder.spring.datajpa.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.bezkoder.spring.datajpa.model.Tutorial;
import com.bezkoder.spring.datajpa.repository.TutorialRepository;

@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class TutorialController {

  @Autowired
  TutorialRepository tutorialRepository;

  @GetMapping("/tutorials")
  public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
    try {
      List<Tutorial> tutorials = new ArrayList<Tutorial>();

      if (title == null)
        tutorialRepository.findAll().forEach(tutorials::add);
      else
        tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);

      if (tutorials.isEmpty()) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
      }

      return new ResponseEntity<>(tutorials, HttpStatus.OK);
    } catch (Exception e) {
      return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  @GetMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
    Optional<Tutorial> tutorialData = tutorialRepository.findById(id);

    if (tutorialData.isPresent()) {
      return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }

  @PostMapping("/tutorials")
  public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
    try {
      Tutorial _tutorial = tutorialRepository
          .save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false));
      return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
    } catch (Exception e) {
      return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  @PutMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
    Optional<Tutorial> tutorialData = tutorialRepository.findById(id);

    if (tutorialData.isPresent()) {
      Tutorial _tutorial = tutorialData.get();
      _tutorial.setTitle(tutorial.getTitle());
      _tutorial.setDescription(tutorial.getDescription());
      _tutorial.setPublished(tutorial.isPublished());
      return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }

  @DeleteMapping("/tutorials/{id}")
  public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
    try {
      tutorialRepository.deleteById(id);
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    } catch (Exception e) {
      return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  @DeleteMapping("/tutorials")
  public ResponseEntity<HttpStatus> deleteAllTutorials() {
    try {
      tutorialRepository.deleteAll();
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    } catch (Exception e) {
      return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

  }

  @GetMapping("/tutorials/published")
  public ResponseEntity<List<Tutorial>> findByPublished() {
    try {
      List<Tutorial> tutorials = tutorialRepository.findByPublished(true);

      if (tutorials.isEmpty()) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
      }
      return new ResponseEntity<>(tutorials, HttpStatus.OK);
    } catch (Exception e) {
      return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

}

@CrossOrigin is for configuring allowed origins.
@RestController annotation is used to define a controller and to indicate that the return value of the methods should be be bound to the web response body.
@RequestMapping("/api") declares that all Apis’ url in the controller will start with /api.
– We use @Autowired to inject TutorialRepository bean to local variable.

More information: @RestController vs @Controller annotation

Run & Test

Run Spring Boot application with command: mvn spring-boot:run.

tutorials table will be automatically generated in Database.
If you check MySQL for example, you can see things like this:

mysql> describe tutorials;

+--------------+--------------+------+-----+---------+-------+
| Field        | Type         | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id           | bigint(20)   | NO   | PRI | NULL    |       |
| description  | varchar(255) | YES  |     | NULL    |       |
| published    | bit(1)       | YES  |     | NULL    |       |
| title        | varchar(255) | YES  |     | NULL    |       |
+--------------+--------------+------+-----+---------+-------+

Now we can check the CRUD operations with Postman.

Create some Tutorials:

spring-boot-data-jpa-crud-example-create-tutorial

mysql> select * from tutorials;

+----+-----------------------+--------------+-------------------------+
| id | description           | published    | title                   |
+----+-----------------------+--------------+-------------------------+
|  1 | Description for Tut#1 | 0            | Spring Boot Tutorial #1 |
|  2 | Description for Tut#2 | 0            | Spring Boot Tutorial #2 |
|  3 | Description for Tut#3 | 0            | Spring Boot Tutorial #3 |
|  4 | Tut#4 Description     | 0            | Spring Data Tutorial #4 |
|  5 | Tut#5 Description     | 0            | Spring Data Tutorial #5 |
+----+-----------------------+--------------+-------------------------+

Update some Tutorials:

spring-boot-data-jpa-crud-example-update-tutorial

mysql> select * from tutorials;

+----+-----------------------+--------------+-------------------------+
| id | description           | published    | title                   |
+----+-----------------------+--------------+-------------------------+
|  1 | Description for Tut#1 | 0            | Spring Boot Tutorial #1 |
|  2 | Desc for Tut#2        | 1            | Spring Tutorial #2      |
|  3 | Desc for Tut#3        | 1            | Spring Boot Tutorial #3 |
|  4 | Tut#4 Description     | 0            | Spring Data Tutorial #4 |
|  5 | Tut#5 Desc            | 1            | Spring Data Tutorial #5 |
+----+-----------------------+--------------+-------------------------+

Get all Tutorials:

spring-boot-data-jpa-crud-example-retrieve-tutorials

Get a Tutorial by Id:

spring-boot-data-jpa-crud-example-retrieve-a-tutorial

Find all published Tutorials:

spring-boot-data-jpa-crud-example-find-published-tutorials

Find all Tutorials which title contains ‘boot’:

spring-boot-data-jpa-crud-example-find-tutorials

Delete a Tutorial:

spring-boot-data-jpa-crud-example-delete-a-tutorial

mysql> select * from tutorials;

+----+-----------------------+--------------+-------------------------+
| id | description           | published    | title                   |
+----+-----------------------+--------------+-------------------------+
|  1 | Description for Tut#1 | 0            | Spring Boot Tutorial #1 |
|  2 | Desc for Tut#2        | 1            | Spring Tutorial #2      |
|  3 | Desc for Tut#3        | 1            | Spring Boot Tutorial #3 |
|  5 | Tut#5 Desc            | 1            | Spring Data Tutorial #5 |
+----+-----------------------+--------------+-------------------------+

Delete all Tutorials:

spring-boot-data-jpa-crud-example-delete-all-tutorials

mysql> select * from tutorials;
Empty set (0.00 sec)

You can use the Simple HTTP Client using Axios to check it.

axios-request-example-get-post-put-delete

Or: Simple HTTP Client using Fetch API

Conclusion

Today we’ve built a Rest CRUD API using Spring Boot, Spring Data JPA, Hibernate, Maven to interact with MySQL/PostgreSQL. We also see that JpaRepository supports a great way to make CRUD operations and custom finder methods without need of boilerplate code. Finally, we check our CRUD example with Postman successfully.

You can also make custom query with @Query annotation:
Spring JPA @Query example: Custom query in Spring Boot

If you want to add Pagination to this Spring project, you can find the instruction at:
Spring Boot Pagination & Filter example | Spring JPA, Pageable

To sort/order by multiple fields:
Spring Data JPA Sort/Order by multiple Columns | Spring Boot

Handle Exception for this Rest APIs is necessary:
Spring Boot @ControllerAdvice & @ExceptionHandler example
@RestControllerAdvice example in Spring Boot

Or way to write Unit Test:
Spring Boot Unit Test for JPA Repository
Spring Boot Unit Test for Rest Controller

You can also know:
Validate Request Body in Spring Boot
– how to deploy this Spring Boot App on AWS (for free) with this tutorial.
– dockerize with Docker Compose: Spring Boot and MySQL example
– or: Docker Compose: Spring Boot and Postgres example
– way to upload an Excel file and store the data in MySQL database with this post
– upload CSV file and store the data in MySQL with this post.

Happy learning! See you again.

Further Reading

Fullstack CRUD App:
Spring Boot Thymeleaf example
Vue + Spring Boot example
Angular 8 + Spring Boot example
Angular 10 + Spring Boot example
Angular 11 + Spring Boot example
Angular 12 + Spring Boot example
Angular 13 + Spring Boot example
Angular 14 + Spring Boot example
Angular 15 + Spring Boot example
Angular 16 + Spring Boot example
React + Spring Boot example

We can improve the example by adding Comments for each Tutorial. It is the One-to-Many Relationship and I write a tutorial for this at:
Spring Boot One To Many example with JPA, Hibernate

Or add Tags with Many-to-Many Relationship:
Spring Boot Many to Many example with JPA, Hibernate

Source Code

You can find the complete source code for this tutorial on Github.

Rest API without Controller: Spring Data REST example in Spring Boot

More Derived queries at:
JPA Repository query example in Spring Boot

Using JdbcTemplate instead:
Spring Boot JdbcTemplate example with MySQL: CRUD App

Reactive Rest API:
Spring Boot WebFlux Rest API example

Other Databases:
Spring Boot, PostgreSQL: Rest Apis example with Spring JPA
Spring Boot, Oracle: Rest Apis example with Spring JPA

Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
Caching: Spring Boot Redis Cache example

26 thoughts to “Spring Boot, Spring Data JPA – Rest CRUD API example”

  1. Cheer ! I’m well done this tutorial ! thanks for made my day of Starting Spring Boot.

  2. I’m still learning from you, but I’m making my way to the top as well. I definitely enjoy reading everything that is posted on your website. Keep the posts coming. I loved it!

  3. excellent tutorial . i had challenges with all methods except the one for creating a tutorial . the error rendered was …. No default constructor for entity: ……. it took a while to figure it out but eventually i added a default constructor without any args and they all worked public Tutorial(){ }

  4. Hi, bezkoder. I appreciate your work.
    what is the advantage of using updateTutorial(@PathVariable(“id”) long id, @RequestBody Tutorial tutorial)
    instead of updateTutorial( @RequestBody Tutorial tutorial) ?

  5. Amazing Article. Thank you so much for providing important and extra more information of RESTFul API.

  6. When i used your code in mine, i found “Invalid CORS Request”. But mine Spring boot version is ” 2.1.16 “. So i added one Configuration file for overcome that problem. Just information.
    Thank you for your code.

    1. Hi, could you show your configuration file to help other people who have the same issue as you? :)

      1. please check the following code. It works for me.

        @Configuration
        public class CorsConfig {
        
            @Bean
            public WebMvcConfigurer corsConfigurer() {
                return new WebMvcConfigurerAdapter() {
                    @Override
                    public void addCorsMappings(CorsRegistry registry) {
                        registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*")
                                .allowedHeaders("*");
                    }
                };
            }
        }
        
        1. Hi, can you sharre more about this. May be your full file or something because I create a config file in config package but my file has some many warning. And it still won’t work

  7. hello, very nice tutorial, explanation is very good and easy to understand, i have my concepts clear about writing REST API’s, all working good, but is there any API for the title function?
    thanks again.

  8. Very detailed and helpful!
    Could you please add how do we bulk update several records at the same time.
    Thanks.

  9. I loved this. Amazing explanations😊
    Please give more explanation for last part rest is very good 👍

  10. Hi, I’m making a todo-list and want to have Angular frontend so I follow your tutorials. I already have Sprinboot app but I made changes to make it REST according to this tutorial.

    I am having trouble understanding a recent java syntax in the TutorialController.

    Could you explain the syntax/meaning of

    tutorials::add

    in the forEachs in the getAllTutorials() method ?

    Maybe there is an older alternative syntax, because I get a the 2nd usage that it’s meant to add to list probably. However the first one would have title null so nothing to add, no element from list outputed from call to repository.

    Second question: I understand when we add a tutorial, we get a 201 http response (not 200) and it’s fine but I have trouble with findAll() getting it to return a JSON. I added jackson dependency in POM and I am using syntax near the method: @RequestMapping(value”/all”, method= RequestMethod.GET, produces: “application/json”)
    I stopped using @GetMapping @PostMapping @PutMapping @DeleteMapping switching like this for RequestMethod. Also I’m not sure produces:”application/json” need to be in every methods. I don’t get any JSON for findAll() at present although i get a 200 ok. But I guess I have to parameterize json output into the response.

Comments are closed to reduce spam. If you have any question, please send me an email.