You are here
Home > java >

How to develop REST CRUD API using Spring Boot ?

How to develop REST CRUD API using Spring Boot ?It will not be an overstatement if I say that we can’t develop an enterprise application without using webservices as an integration layer. Generally, we develop webservices in the form of either a producer or a consumer or both. However Producer is very important for us because we develop it in Java only. Also, we have full control of database interaction logic implementation with us. Now you might have interpreted the significance of our article ‘How to develop REST CRUD API using Spring Boot?’. On the other hand, Consumer can be Angular Application, ReactJS Application, Android Device, iOS Device and many others or even our favorite java based RestTemplate(Spring Boot REST Client).

Building a RESTful CRUD (Create, Read, Update, Delete) API is a common task in modern web development. Spring Boot, a powerful and modernized framework, makes this process straightforward. Our focus in this article is on developing producer API(REST API) using Spring Boot. Now let’s discuss on ‘How to develop REST CRUD API using Spring Boot ?’ without leaving any theoretical concept which are very essential to know.

What will you learn from this article?

Once you complete going through all points of this article, You will be able to answer :

1) What is REST and REST API in the context of Web services?

2) How to create a Spring Boot REST application that incorporates industry level project design ?

3) How to develop CRUD (Create, Retrieve, Update, Delete) operations that can be used by any other even non-java application ?

4) How to write bug free CRUD operations, including exceptions & exception handlers?

5) Equally important, How to use annotations @RestController, @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping, @Modifying, @Query, @Transactional, @RestControllerAdvice, @ExceptionHandler, @ControllerAdvice, @ResponseBody, @RequestBody, @PathVariable, @Data, @NoArgsConstructor, @AllArgsConstructor, @Entity, @Component, @Service, @Autowired ?

6) How to work with Spring Boot Data JPA repository interface?

7) How to write modular & reusable code?

8) Moreover, How to implement dynamic code with minimal changes, keeping future change requests in mind?

9) How to develop an integration layer to get interoperability ?

10) How to test REST Application by supplying & receiving JSON data ?

11) Last but not the least you will learn “How to develop REST CRUD API using Spring Boot ?”

What is REST?

REST stands for Representational State Transfer. It transfers state(data) in global format(representational) between two different applications running on different servers. In the process of data transfer, who requests data is called Consumer/Client application and who provides data is called a producer application. REST is an architectural style that follows a set of rules to create webservices. Webservices provide reusable data to multiple applications and interoperability between them on the internet. Web services that conform to the REST architectural style, called RESTful Web services.

How to develop REST CRUD API using Spring Boot ?

Developing a Rest API is nothing but creating classes & methods in a specific architectural style so that data can be reused between interoperable applications. More or less we create RestController and respective CRUD operations in the process of development of the REST API.

What is RestController ?

In Spring Boot REST programming RestController is a mandatory class which acts as a front controller. It contains several methods that return Body and Status as a ResponseEntity object. Body refers to data in form of String, Object, Collections etc. Whereas Status refers to the HttpResponse Status (200, 404,405, 500 etc.).

Software used in this Project ?

–STS (Spring Tool Suite) : Version-> 4.7.1.RELEASE
–MySQL Database : Version ->8.0.19 MySQL Community Server
–JDK8 or later versions (Extremely tested on JDK8, JDK9 and JDK14)

Coding Steps

Step#1 : Create Project in STS

If you are new to Spring Boot, visit a separate article on how to create a sample project in spring boot. While creating project in STS add 4 starters ‘MySql Driver’, ‘Spring Data JPA’, ‘Spring Web’ and ‘Lombok’. You can also add ‘Spring Boot DevTools’ optionally. If you are new to ‘Lombok’, kindly visit ‘How to configure Lombok‘ and to know all about it in detail.

Step#2 : Create Database 

You can use database software of your own choice and create a Database accordingly. We have used MySQL database software. Login to your MySQL and execute a Database creation query. In our case DB Name is ‘REST_INVOICE’, so our query will be ‘ CREATE DATABSE REST_INVOICE’.

Step#3 : Update application.properties and Write Classes & methods(REST API) 

Let’s consider ‘Invoice’ as a model to develop the REST API. Invoice will have many fields such as invoiceNumber, invoiceName, invoiceAmount etc.. Below Table will show the list of classes & other files used in the project accordingly.

Package/Location Class/Interface/file namePurpose
src/main/resourcesapplication.propertiesproperties file to declare common properties in the project
com.dev.invoice.rest.entityInvoice.javaModel/Entity class with Database table mapping
com.dev.invoice.rest.repoInvoiceRepository.javaRepository Interface which extends JpaRepository interface
com.dev.invoice.rest.serviceIInvoiceService.javaService interface with Database related methods
com.dev.invoice.rest.service.implInvoiceServiceImpl.javaService class contains implementations of methods declared in Service interface for Database related operations
com.dev.invoice.rest.entityErrorType.javaHelper class used in InvoiceErrorHandler.java
com.dev.invoice.rest.exceptionInvoiceNotFoundException.javaTo define custom Exception if any Invoice not found
com.dev.invoice.rest.exception.handlerInvoiceErrorHandler.javaTo handle the error In case InvoiceNotFoundException is thrown
from any controller method
com.dev.invoice.rest.utilInvoiceUtil.javaUtility class to maximize code reusability & minimize code redundancy.
com.dev.invoice.rest.controllerInvoiceRestController.javaRest Controller the backbone of REST API, accepts all requests coming from client and handover to respective method for processing.
application.properties
#  DB Connection Properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/REST_INVOICE
spring.datasource.username=root
spring.datasource.password=devs

# JPA Properites
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
Invoice.java
package com.dev.invoice.rest.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Invoice {

@Id
@GeneratedValue
private Long id;
private String name;
private Double amount;
private Double finalAmount;
private String number;
private String receivedDate;
private String type;
private String vendor;
private String comments;
}

This POJO class representing an entity called “Invoice” uses JPA (Java Persistence API) annotations for object-relational mapping.

  1. Annotations: The class uses various annotations from the javax.persistence package to define the mapping of the class to a relational database.

  2. Lombok Annotations: The @Data, @NoArgsConstructor, and @AllArgsConstructor annotations are from the Lombok library, which automatically generates boilerplate code for getters, setters, constructors, and other common methods.

  3. @Entity: An annotation indicating that the class is a JPA entity, meaning it is associated with a table in the database.

  4. @Id: An annotation used to specify the primary key of the entity.

  5. @GeneratedValue: An annotation to specify that the ID should be automatically generated.

  6. Attributes: The class has several attributes representing different properties of an invoice such as id, name, amount, finalAmount, number, receivedDate, type, vendor, and comments.

This class is designed to map invoice objects to a database table using JPA and Lombok annotations (to reduce boilerplate code).

InvoiceRepository.java
package com.dev.invoice.rest.repo;

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

import com.dev.invoice.rest.entity.Invoice;

public interface InvoiceRepository extends JpaRepository<Invoice, Long>{

// Update is Non-Select Operation, so @Modifying is used
@Modifying
@Query("UPDATE Invoice SET number=:number WHERE id=:id")
Integer updateInvoiceNumberById(String number,Long id);
}
Service Interface IInvoiceService.java
package com.dev.invoice.rest.service;

import java.util.List;

import com.dev.invoice.rest.entity.Invoice;

public interface IInvoiceService {

/**
* Takes Invoice Object as input and returns PK generated
*/
Long saveInvoice(Invoice inv);

/**
* Takes existing Invoice data as input and updates values
*/
void updateInvoice(Invoice e);

/**
* Takes PK(ID) as input and deletes Invoice Object data
*/
void deleteInvoice(Long id);

/**
* Takes id as input and returns one row as one object
*/
Invoice getOneInvoice(Long id); //used in RestController

/**
* select all rows and provides result as a List<Invoice>
*/
List<Invoice> getAllInvoices();

/**
* Takes Id as input,checks if record exists returns true, else false
*
*/
boolean isInvoiceExist(Long id);

/**
* Takes 2 fields as input, updates Invoice data as provided where clause
* like 'UPDATE Invoice SET number=:number WHERE id=:id'
*/
Integer updateInvoiceNumberById(String number,Long id);
}
InvoiceServiceImpl.java
package com.dev.invoice.rest.service.impl;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.dev.invoice.rest.entity.Invoice;
import com.dev.invoice.rest.exception.InvoiceNotFoundException;
import com.dev.invoice.rest.repo.InvoiceRepository;
import com.dev.invoice.rest.service.</yoastmark>IInvoiceService;
import com.dev.invoice.rest.util.InvoiceUtil;

@Service
public class InvoiceServiceImpl implements IInvoiceService {

@Autowired
private InvoiceRepository repo;

@Autowired
private InvoiceUtil util;

@Override
public Long saveInvoice(Invoice inv) {
util.CalculateFinalAmountIncludingGST(inv);
Long id = repo.save(inv).getId();
return id;
}

@Override
public void updateInvoice(Invoice inv) {
util.CalculateFinalAmountIncludingGST(inv);
repo.save(inv);
}

@Override
public void deleteInvoice(Long id) {
Invoice inv= getOneInvoice(id);
repo.delete(inv);
}

public Optional<Invoice> getSingleInvoice(Long Id) {
return repo.findById(Id);
}

@Override
public Invoice getOneInvoice(Long id) {

Invoice inv = repo.findById(id)
.orElseThrow(()->new InvoiceNotFoundException(
new StringBuffer().append("Product '")
.append(id)
.append("' not exist")
.toString())
);
return inv;
}

@Override
public List<Invoice> getAllInvoices() {
List<Invoice> list = repo.findAll();
//JDK 1.8 List Sort (using Comparator)
list.sort((ob1,ob2)->ob1.getId().intValue()-ob2.getId().intValue());
//list.sort((ob1,ob2)->ob1.getAmount().compareTo(ob2.getAmount())); //ASC
//list.sort((ob1,ob2)->ob2.getAmount().compareTo(ob1.getAmount())); // DESC
return list;
}

@Override
public boolean isInvoiceExist(Long id) {

return repo.existsById(id);
}

@Override
@Transactional
public Integer updateInvoiceNumberById(
String number, Long id)
{
if(!repo.existsById(id)) {
throw new InvoiceNotFoundException(
new StringBuffer()
.append("Invoice '")
.append(id)
.append("' not exist")
.toString());
}
return repo.updateInvoiceNumberById(number, id);
}

}
ErrorType.java
package com.dev.invoice.rest.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorType {

private String time;
private String status;
private String message;

}
InvoiceNotFoundException.java
package com.dev.invoice.rest.exception;

//Custom Exception
public class InvoiceNotFoundException extends RuntimeException{

private static final long serialVersionUID = 1L;

public InvoiceNotFoundException() {
super();
}

public InvoiceNotFoundException(String message) {
super(message);
}

}
InvoiceErrorHandler.java
package com.dev.invoice.rest.exception.handler;

import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.dev.invoice.rest.entity.ErrorType;
import com.dev.invoice.rest.exception.InvoiceNotFoundException;

//@ControllerAdvice
@RestControllerAdvice
public class InvoiceErrorHandler {
/**
* In case of InvoiceNotFoundException is thrown
* from any controller method, this logic gets
* executed which behaves like re-usable and
* clear code (Code Modularity)
* @param nfe
* @return ResponseEntity
*/
//@ResponseBody
@ExceptionHandler(InvoiceNotFoundException.class)
public ResponseEntity<ErrorType> handleNotFound(InvoiceNotFoundException nfe){

return new ResponseEntity<ErrorType>(
new ErrorType(
new Date(System.currentTimeMillis()).toString(),
"404- NOT FOUND",
nfe.getMessage()),
HttpStatus.</yoastmark>NOT_FOUND);
}
}
Utility class InvoiceUtil.java
package com.dev.invoice.rest.util;

import org.springframework.stereotype.Component;

import com.dev.invoice.rest.entity.Invoice;

@Component
public class InvoiceUtil {

public Invoice CalculateFinalAmountIncludingGST (Invoice inv) {
var amount=inv.getAmount();
var gst= 0.1;
var finalAmount=amount+(amount*gst);
inv.setFinalAmount(finalAmount);
return inv;
}

public void copyNonNullValues(Invoice req, Invoice db) {

if(req.getName() !=null) {
db.setName(req.getName());
}

if(req.getAmount() !=null) {
db.setAmount(req.getAmount());
}

if(req.getNumber() !=null) {
db.setNumber(req.getNumber());
}

if(req.getReceivedDate() !=null) {
db.setReceivedDate(req.getReceivedDate());
}

if(req.getType() !=null) {
db.setType(req.getType());
}

if(req.getVendor() !=null) {
db.setVendor(req.getVendor());
}

if(req.getComments() !=null) {
db.setComments(req.getComments());
}
}
}
InvoiceRestController.java
package com.dev.invoice.rest.controller;

import java.util.List;

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.PatchMapping;
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.RestController;

import com.dev.invoice.rest.entity.Invoice;
import com.dev.invoice.rest.exception.InvoiceNotFoundException;
import com.dev.invoice.rest.service.IInvoiceService;
import com.dev.invoice.rest.util.InvoiceUtil;

@RestController
@RequestMapping("/api")
// @CrossOrigin(origins = "http://localhost:4200") //Required in case of Angular Client
public class InvoiceRestController {

@Autowired
private IInvoiceService service;

@Autowired
private InvoiceUtil util;

/**
* Takes Invoice Object as input and returns save Status as ResponseEntity<String>
*/
@PostMapping("/invoices")
public ResponseEntity<String> saveInvoice(@RequestBody Invoice inv){
ResponseEntity<String> resp = null;
try{
Long id = service.saveInvoice(inv);
resp= new ResponseEntity<String>(
"Invoice '"+id+"' created",HttpStatus.CREATED); //201-created
} catch (Exception e) {
e.printStackTrace();
resp = new ResponseEntity<String>(
"Unable to save Invoice",
HttpStatus.INTERNAL_SERVER_ERROR); //500-Internal Server Error
}
return resp;
}

/**
* To retrieve all Invoices, returns data retrieval Status as ResponseEntity<?>
*/
@GetMapping("/invoices")
public ResponseEntity<?> getAllInvoices() {
ResponseEntity<?> resp=null;
try {
List<Invoice> list= service.getAllInvoices();
resp= new ResponseEntity<List<Invoice>>(list,HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
resp = new ResponseEntity<String>(
"Unable to get Invoice",
HttpStatus.INTERNAL_SERVER_ERROR);
}
return resp;
}

/**
* To retrieve one Invoice by providing id, returns Invoice object & Status as ResponseEntity<?>
*/
@GetMapping("/invoices/{id}")
public ResponseEntity<?> getOneInvoice(@PathVariable Long id){
ResponseEntity<?> resp= null;
try {
Invoice inv= service.getOneInvoice(id);
resp= new ResponseEntity<Invoice>(inv,HttpStatus.OK);
}catch (InvoiceNotFoundException nfe) {
throw nfe;
}catch (Exception e) {
e.printStackTrace();
resp = new ResponseEntity<String>(
"Unable to find Invoice",
HttpStatus.INTERNAL_SERVER_ERROR);
}
return resp;
}

/**
* To delete one Invoice by providing id, returns Status as ResponseEntity<String>
*/
@DeleteMapping("/invoices/{id}")
public ResponseEntity<String> deleteInvoice(@PathVariable Long id){

ResponseEntity<String> resp= null;
try {
service.deleteInvoice(id);
resp= new ResponseEntity<String> (
"Invoice '"+id+"' deleted",HttpStatus.OK);

} catch (InvoiceNotFoundException nfe) {
throw nfe;
} catch (Exception e) {
e.printStackTrace();
resp= new ResponseEntity<String>(
"Unable to delete Invoice", HttpStatus.INTERNAL_SERVER_ERROR);
}

return resp;
}

/**
* To modify one Invoice by providing id, updates Invoice object & returns Status as ResponseEntity<String>
*/
@PutMapping("/invoices/{id}")
public ResponseEntity<String> updateInvoice(@PathVariable Long id, @RequestBody Invoice invoice){

ResponseEntity<String> resp = null;
try {
//db Object
Invoice inv= service.getOneInvoice(id);
//copy non-null values from request to Database object
util.copyNonNullValues(invoice, inv);
//finally update this object
service.updateInvoice(inv);
resp = new ResponseEntity<String>(
//"Invoice '"+id+"' Updated",
HttpStatus.RESET_CONTENT); //205- Reset-Content(PUT)

} catch (InvoiceNotFoundException nfe) {
throw nfe; // re-throw exception to handler
} catch (Exception e) {
e.printStackTrace();
resp = new ResponseEntity<String>(
"Unable to Update Invoice",
HttpStatus.INTERNAL_SERVER_ERROR); //500-ISE
}
return resp;
}

/**
* To update one Invoice just like where clause condition, updates Invoice object & returns Status as ResponseEntity<String>
*/
@PatchMapping("/invoices/{id}/{number}")
public ResponseEntity<String> updateInvoiceNumberById(
@PathVariable Long id,
@PathVariable String number
)
{
ResponseEntity<String> resp = null;
try {
service.updateInvoiceNumberById(number, id);
resp = new ResponseEntity<String>(
"Invoice '"+number+"' Updated",
HttpStatus.PARTIAL_CONTENT); //206- Reset-Content(PUT)

} catch(InvoiceNotFoundException pne) {
throw pne; // re-throw exception to handler
} catch (Exception e) {
e.printStackTrace();
resp = new ResponseEntity<String>(
"Unable to Update Invoice",
HttpStatus.INTERNAL_SERVER_ERROR); //500-ISE
}
return resp;
}
}

Finally your project structure would look like below screen.

How To Develop REST API Using Spring Boot ?

How to run the application ?

Further to run the application for testing purpose, right click on Project and then select Run As >> Spring Boot App. You can also package it into a jar & run it accordingly. Additionally, to test the app you need to have a Client application/software.

How to test the application ?

As discussed in introduction part, there are multiple ways to test the REST application. At this point, we will suggest you to use the most popular tool ‘POSTMAN’. You can also download it from here.

♠ Testing saveInvoice() method : [http://localhost:8080/api/invoices]

Open Postman software, Select method ‘POST’ from dropdown, enter below URL, select ‘Body’ then click on ‘raw’, select ‘JSON” from dropdown. All the above selections are highlighted in the below screenshot. Now Enter data in JSON format. If you want to know more about json, visit a separate article on ‘how to write data in JSON format’. Once you enter the JSON data, click on ‘Send’ button & check the successful message in the lower box.

http://localhost:8080/api/invoices

How to develop REST API using Spring Boot

♦ Testing getAllInvoices() method : [http://localhost:8080/api/invoices]

Select method ‘GET’ from dropdown, then enter below URL. Now click on ‘Send’ button & check the list of all invoices as a JSON format in lower box.

http://localhost:8080/api/invoices

♠ Testing getOneInvoice() method : [http://localhost:8080/api/invoices/{id}]

{id} represents dynamic data. Suppose we want to retrieve Invoice whose id is 2. Select method ‘GET’ from dropdown, then enter below URL. Then click on ‘Send’ button & check the invoice with given id as a JSON format in lower box.

http://localhost:8080/api/invoices/2

♦ Testing updateInvoice() method : [http://localhost:8080/api/invoices/{id}]

Select method ‘PUT’ from dropdown, enter below URL, select ‘Body’ then click on ‘raw’, select ‘JSON” from dropdown. Thereafter Enter data in JSON format to be modified. Then click on ‘Send’ button and check the successful message in lower box.

http://localhost:8080/api/invoices/2

♠ Testing deleteInvoice() method : [http://localhost:8080/api/invoices/{id}]

Suppose you want to remove an Invoice where id is 2. Then use below pattern URL. Select method ‘DELETE’ from dropdown, then enter below URL. Then click on ‘Send’ button and check the successful message in lower box.

http://localhost:8080/api/invoice/2

♦ Testing updateInvoiceNumberById() method : [http://localhost:8080/api/invoices/{id}/{number}]

Suppose you want to modify name field of an Invoice where id is 1. Then use below pattern URL. Select method ‘PATCH’ from dropdown, enter below URL, select ‘Body’ then click on ‘raw’, select ‘JSON” from dropdown. Then Enter data in JSON format to be modified. Finally click on ‘Send’ button & check the successful message in lower box.

http://localhost:8080/api/invoices/1/Inv02345

Can we use this REST API example in the real project ?

Of course. You have to change all occurrences of Entity Name as per your real project, then you may use it accordingly.

FAQ

How can I define API endpoints in Spring Boot?

API endpoints are defined by creating controller classes in Spring Boot. These classes use annotations like @RestController and @RequestMapping to map HTTP methods (GET, POST, PUT, DELETE) to specific Java methods that handle the requests.

What is the role of the @ResponseBody annotation in Spring Boot REST controllers?

The @ResponseBody annotation is used to indicate that the return value of a controller method should be serialized directly to the HTTP response body. It is often used when returning data is in JSON or XML format.

How can I document my Spring Boot REST API for developers and consumers?

We can document our Spring Boot REST API using tools like Swagger, Springfox, or OpenAPI. These tools generate interactive API documentation that helps developers and consumers understand how to use REST API.

How can I test my Spring Boot REST API endpoints?

We can test Spring Boot REST API endpoints using tools like Postman, curl, or by writing unit tests and integration tests using frameworks like JUnit and Spring Test. Moreover, we can write methods using RestTemplate to test the REST API endpoints.

What is the difference between PUT and PATCH methods, and when should I use each in Spring Boot REST APIs?

The PUT method updates or replaces an entire resource, while the PATCH method makes partial modifications to a resource. In Spring Boot REST APIs, we should use PUT when we need to update the entire resource, and PATCH when we want to apply partial updates accordingly.

Conclusion 

Almost every REST application will have these operations that we learnt in this article. To sum up, it is almost impossible to develop a REST API without having these operations. We have learnt the mandatory concepts of ‘How to develop REST CRUD API using Spring Boot ?’. Furthermore, you can go through other article on How to consume REST API using RestTemplate using Spring Boot.

15 thoughts on “How to develop REST CRUD API using Spring Boot ?

  1. I want to say that this post is awesome, great written and include almost all necessary information.

    I would like to see more posts like this .

  2. This is a topic that’s near to my heart… Cheers! Exactly where are your contact details though?

  3. Dear Website Creater,
    Thank you for providing this Excellent article.
    please provide the JUnit and Mockito test cases from basics and multiple test cases in multiple scinarios, because i was seraching many times on google, youtube about the multiple test case scinario but i can’t.
    Please provide this article as soo as posiable.

Leave a Reply


Top