In this tutorial, we will learn how to use the Spring Boot 3.2 RestClient class to make GET, POST, PUT, and DELETE REST API calls. We will first create CRUD REST APIs using Spring Boot, Spring Data JPA, and MySQL database and then we will use the RestClient class to consume these CRUD REST APIs.
Introduction to RestClient Class
The RestClient class is a new addition to Spring Framework 6.1 and Spring Boot 3.2. It provides a functional style blocking HTTP API that is similar in design to WebClient. RestClient is designed to be simpler and easier to use than RestTemplate, and it offers several advantages over RestTemplate, including:
- A fluent API that makes it easy to chain requests together
- Support for annotations to configure requests
- Built-in error handling
- Support for both blocking and non-blocking requests
1. Building CRUD REST APIs using Spring Boot, Spring Data JPA, and MySQL database
Tools and Technologies Used:
- Spring Boot 3.2
- Java 17
- Spring Data JPA
- Hibernate
- MySQL Database
- Maven
1. Add Maven Dependencies
Spring Boot provides a web tool called Spring Initializer to create and bootstrap Spring boot applications quickly. Just go to https://start.spring.io/ and generate a new spring boot project.Open the pom.xml file and add the below Maven dependencies to the project:
<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>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2. Configure MySQL Database
create database employee_management
spring.datasource.url=jdbc:mysql://localhost:3306/employee_management
spring.datasource.username=root
spring.datasource.password=Mysql@123
# Hibernate properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
# create, create-drop
spring.jpa.hibernate.ddl-auto=update
3. Create an Employee JPA Entity
import jakarta.persistence.*;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
@Column(nullable = false, unique = true)
private String email;
}
4. Create Spring Data JPA Repository - EmployeeRepository
import net.javaguides.springboot.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
5. Create DTO Class - EmployeeDto
import lombok.*;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class EmployeeDto {
private Long id;
private String firstName;
private String lastName;
private String email;
}
6. Create a Service Layer
Create EmployeeService Interface
import net.javaguides.springboot.dto.EmployeeDto;
import java.util.List;
public interface EmployeeService {
EmployeeDto createEmployee(EmployeeDto employeeDto);
EmployeeDto getEmployeeById(Long employeeId);
List<EmployeeDto> getAllEmployees();
EmployeeDto updateEmployee(EmployeeDto employeeDto);
void deleteEmployee(Long employeeId);
}
Create UserServiceImpl Class
import lombok.AllArgsConstructor;
import net.javaguides.springboot.converter.EmployeeConverter;
import net.javaguides.springboot.dto.EmployeeDto;
import net.javaguides.springboot.entity.Employee;
import net.javaguides.springboot.service.EmployeeService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeRepository employeeRepository;
@Override
public EmployeeDto createEmployee(EmployeeDto employeeDto) {
Employee employee = EmployeeConverter.mapToEmployee(employeeDto);
Employee savedEmployee = employeeRepository.save(employee);
return EmployeeConverter.mapToEmployeeDto(savedEmployee);
}
@Override
public EmployeeDto getEmployeeById(Long employeeId) {
// we need to check whether employee with given id is exist in DB or not
Employee existingEmployee = employeeRepository.findById(employeeId)
.orElseThrow(() -> new IllegalArgumentException(
"Employee not exists with a given id : " + employeeId)
);
return EmployeeConverter.mapToEmployeeDto(existingEmployee);
}
@Override
public List<EmployeeDto> getAllEmployees() {
List<Employee> employees = employeeRepository.findAll();
return employees.stream()
.map(employee -> EmployeeConverter.mapToEmployeeDto(employee))
.collect(Collectors.toList());
}
@Override
public EmployeeDto updateEmployee(EmployeeDto employeeDto) {
// we need to check whether employee with given id is exist in DB or not
Employee existingEmployee = employeeRepository.findById(employeeDto.getId())
.orElseThrow(() -> new IllegalArgumentException(
"Employee not exists with a given id : " + employeeDto.getId())
);
// convert EmployeeDto to Employee JPA entity
Employee employee = EmployeeConverter.mapToEmployee(employeeDto);
return EmployeeConverter.mapToEmployeeDto(employeeRepository.save(employee));
}
@Override
public void deleteEmployee(Long employeeId) {
// we need to check whether employee with given id is exist in DB or not
Employee existingEmployee = employeeRepository.findById(employeeId)
.orElseThrow(() -> new IllegalArgumentException(
"Employee not exists with a given id : " + employeeId)
);
employeeRepository.deleteById(employeeId);
}
}
7. Create Controller Layer - EmployeeController
package net.javaguides.springboot.controller;
import lombok.AllArgsConstructor;
import net.javaguides.springboot.dto.EmployeeDto;
import net.javaguides.springboot.service.EmployeeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@AllArgsConstructor
@RequestMapping("api/employees")
public class EmployeeController {
private EmployeeService employeeService;
// build create employee REST API
@PostMapping
public ResponseEntity<EmployeeDto> createEmployee(@RequestBody EmployeeDto employee){
EmployeeDto savedEmployee = employeeService.createEmployee(employee);
return new ResponseEntity<>(savedEmployee, HttpStatus.CREATED);
}
// build get employee by id REST API
// http://localhost:8080/api/employees/1
@GetMapping("{id}")
public ResponseEntity<EmployeeDto> getEmployeeById(@PathVariable("id") Long employeeId){
EmployeeDto employee = employeeService.getEmployeeById(employeeId);
//return new ResponseEntity<>(employee, HttpStatus.OK);
return ResponseEntity.ok(employee);
}
// build get all employees REST API
@GetMapping
public ResponseEntity<List<EmployeeDto>> getAllEmployees(){
List<EmployeeDto> employees = employeeService.getAllEmployees();
return new ResponseEntity<>(employees, HttpStatus.OK);
}
// build update employee REST API
// http://localhost:8080/api/employees/1
@PutMapping("{id}")
public ResponseEntity<EmployeeDto> updateEmployee(@PathVariable("id") long id
,@RequestBody EmployeeDto employeeDto){
employeeDto.setId(id);
EmployeeDto updatedEmployee = employeeService.updateEmployee(employeeDto);
return new ResponseEntity<EmployeeDto>(updatedEmployee, HttpStatus.OK);
}
// build delete employee REST API
// http://localhost:8080/api/employees/1
@DeleteMapping("{id}")
public ResponseEntity<String> deleteEmployee(@PathVariable("id") long id){
// delete employee from DB
employeeService.deleteEmployee(id);
return new ResponseEntity<String>("Employee deleted successfully!.", HttpStatus.OK);
}
}
8. Running Spring Boot Application
2. Consume the CRUD REST APIs using the RestClient class
1. Creating a RestClient
public class RestClientTest {
private final RestClient restClient;
public RestClientTest() {
restClient = RestClient.builder()
.baseUrl("http://localhost:8080")
.build();
}
}
2. Use POST to Create a Resource
@Test
public void createEmployee() {
EmployeeDto newEmployee = new EmployeeDto(null, "admin", "admin", "admin123@gmail.com");
EmployeeDto savedEmployee = restClient.post()
.uri("/api/employees")
.contentType(MediaType.APPLICATION_JSON)
.body(newEmployee)
.retrieve()
.body(EmployeeDto.class);
System.out.println(savedEmployee.toString());
}
2. Use GET to Retrieve a Resource
@Test
public void getEmployeeById() {
Long employeeId = 1L;
EmployeeDto employeeDto = restClient.get()
.uri("/api/employees/{id}", employeeId)
.retrieve()
.body(EmployeeDto.class);
System.out.println(employeeDto);
}
3. Use UPDATE to Update an Existing Resource
@Test
public void updateEmployee() {
Long employeeId = 1L;
EmployeeDto updatedEmployee = new EmployeeDto();
updatedEmployee.setFirstName("Ramesh");
updatedEmployee.setLastName("Fadatare");
updatedEmployee.setEmail("ramesh@gmail.com");
EmployeeDto result = restClient.put()
.uri("/api/employees/{id}", employeeId)
.contentType(MediaType.APPLICATION_JSON)
.body(updatedEmployee)
.retrieve()
.body(EmployeeDto.class);
System.out.println(result.toString());
}
5. Use GET to Retrieve all the Resources
@Test
public void findAll() {
List<EmployeeDto> listOfEmployees = restClient.get()
.uri("/api/employees")
.retrieve()
.body(new ParameterizedTypeReference<List<EmployeeDto>>() {});
listOfEmployees.forEach(employeeDto -> {
System.out.println(employeeDto.toString());
});
}
6. Use DELETE to Delete an Existing Resource
@Test
public void deleteEmployee() {
Long employeeId = 1L;
String response = restClient.delete()
.uri("/api/employees/{id}", employeeId)
.retrieve()
.body(String.class);
System.out.println(response);
}
Complete Code
import net.javaguides.springboot.dto.EmployeeDto;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestClient;
import java.util.List;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RestClientTest {
private final RestClient restClient;
public RestClientTest() {
restClient = RestClient.builder()
.baseUrl("http://localhost:8080")
.build();
}
@Order(1)
@Test
public void createEmployee() {
EmployeeDto newEmployee = new EmployeeDto(null, "admin", "admin", "admin123@gmail.com");
EmployeeDto savedEmployee = restClient.post()
.uri("/api/employees")
.contentType(MediaType.APPLICATION_JSON)
.body(newEmployee)
.retrieve()
.body(EmployeeDto.class);
System.out.println(savedEmployee.toString());
}
@Order(2)
@Test
public void getEmployeeById() {
Long employeeId = 1L;
EmployeeDto employeeDto = restClient.get()
.uri("/api/employees/{id}", employeeId)
.retrieve()
.body(EmployeeDto.class);
System.out.println(employeeDto);
}
@Order(3)
@Test
public void updateEmployee() {
Long employeeId = 1L;
EmployeeDto updatedEmployee = new EmployeeDto();
updatedEmployee.setFirstName("Ramesh");
updatedEmployee.setLastName("Fadatare");
updatedEmployee.setEmail("ramesh@gmail.com");
EmployeeDto result = restClient.put()
.uri("/api/employees/{id}", employeeId)
.contentType(MediaType.APPLICATION_JSON)
.body(updatedEmployee)
.retrieve()
.body(EmployeeDto.class);
System.out.println(result.toString());
}
@Order(4)
@Test
public void findAll() {
List<EmployeeDto> listOfEmployees = restClient.get()
.uri("/api/employees")
.retrieve()
.body(new ParameterizedTypeReference<List<EmployeeDto>>() {});
listOfEmployees.forEach(employeeDto -> {
System.out.println(employeeDto.toString());
});
}
@Order(5)
@Test
public void deleteEmployee() {
Long employeeId = 1L;
String response = restClient.delete()
.uri("/api/employees/{id}", employeeId)
.retrieve()
.body(String.class);
System.out.println(response);
}
}
Output:
7. Exception Handling
The RestClient throws two types of exceptions for a failed request: - HttpClientErrorException: with 4xx response code
- HttpServerErrorException: with 5xx response code
- HttpClientErrorException: with 4xx response code
- HttpServerErrorException: with 5xx response code
7.1 Handling HttpClientErrorException Example
@Test
public void exceptionHandlingClientErrorDemo(){
HttpClientErrorException thrown = Assertions.assertThrows(HttpClientErrorException.class,
() -> {
EmployeeDto employee = restClient.get()
.uri("/employees/404")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(EmployeeDto.class);
});
Assertions.assertEquals(404, thrown.getStatusCode().value());
}
@Test
public void exceptionHandlingClientErrorDemo(){
HttpClientErrorException thrown = Assertions.assertThrows(HttpClientErrorException.class,
() -> {
EmployeeDto employee = restClient.get()
.uri("/employees/404")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(EmployeeDto.class);
});
Assertions.assertEquals(404, thrown.getStatusCode().value());
}
7.2 Handling HttpServerErrorException Example
@Test
public void exceptionHandlingServerErrorDemo(){
HttpServerErrorException thrown = Assertions.assertThrows(HttpServerErrorException.class,
() -> {
EmployeeDto employee = restClient.get()
.uri("/api/employees/500")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(EmployeeDto.class);
});
Assertions.assertEquals(500, thrown.getStatusCode().value());
}
@Test
public void exceptionHandlingServerErrorDemo(){
HttpServerErrorException thrown = Assertions.assertThrows(HttpServerErrorException.class,
() -> {
EmployeeDto employee = restClient.get()
.uri("/api/employees/500")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(EmployeeDto.class);
});
Assertions.assertEquals(500, thrown.getStatusCode().value());
}
Comments
Post a Comment
Leave Comment