Now that we already have one REST API working, let's improving with using a better response code and control all exception with a global class.
Create a global class to handle exceptions
First create a record class ExceptionResponse inside the new folder exceptions to return information about errors that occurred.
package com.spring.exceptions;
import java.time.Instant;
public record ExceptionResponse(Instant data, String message, String details) {
}
This record has a variable to return the instant that error occurred, and a message with details.
Now let's create a expeception ResourceNotFoundException
inside the folder exceptions to use when it isn't find a specific entity, in this case a Product.
package com.spring.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String messageError) {
super(messageError);
}
}
The @ResponseStatus annotation are used to inform the default return code for that exception, in this case NOT_FOUND = 404.
To finish this part, it's necessary create a global class, create a class CustomizedResponseEntityExceptionHandler inside the folder exceptions a new folder handler.
package com.spring.exceptions.handler;
import com.spring.exceptions.ExceptionResponse;
import com.spring.exceptions.ResourceNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import java.time.Instant;
@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity<ExceptionResponse> handlerAllExceptions(Exception exception, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(Instant.now(), exception.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(ResourceNotFoundException.class)
public final ResponseEntity<ExceptionResponse> handlerNotFoundExceptions(Exception exception, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(Instant.now(), exception.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
}
}
This class is using the @ControllerAdvice annotation to deal with all exceptions in the app, it's a way to control in only one place. In this part you decide with is better to control case a case or all in one.
This class is a Controller because use @RestController, and each method with @ExceptionHandler annotation means that when that exception occurs that method will be responsible for the treatment and return.
We are using a global Exception to deal with the global expection and ResourceNotFoundException for the situation mentioned before.
Use ResourceNotFoundException in ProductService
Now let's improve the methods, first with the method findById.
public Product findById(Long id) {
return repository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Not found Product with this ID"));
}
When not found a product with the given ID, exception will be thrown.
About the update and delete, only refractory to use findById.
@Transactional
public Product update(Product product, Long id) {
var productPersisted = findById(id);
BeanUtils.copyProperties(product, productPersisted, "id");
return repository.save(productPersisted);
}
@Transactional
public void delete(Long id) {
var productPersisted = findById(id);
repository.delete(productPersisted);
}
Use ResponseEntity in ResponseEntity
All methods need to return a ResponseEntity with the perfect code to that situation, so look the below change.
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Product> create(@RequestBody ProductCreateUpdate productCreate) {
Product product = new Product();
BeanUtils.copyProperties(productCreate, product);
return ResponseEntity.status(HttpStatus.CREATED).body(service.create(product));
}
@GetMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Product> findById(@PathVariable(value = "id") Long id) {
return ResponseEntity.status(HttpStatus.OK).body(service.findById(id));
}
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Product>> findAll() {
var products = service.findAll();
if (products.isEmpty()) return ResponseEntity.noContent().build();
return ResponseEntity.status(HttpStatus.OK).body(products);
}
@PutMapping(path = "/{id}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Product> update(@PathVariable(value = "id") Long id, @RequestBody ProductCreateUpdate productUpdate) {
Product product = new Product();
BeanUtils.copyProperties(productUpdate, product);
return ResponseEntity.status(HttpStatus.OK).body(service.update(product, id));
}
@DeleteMapping(path = "/{id}")
public ResponseEntity<?> delete(@PathVariable(value = "id") Long id) {
service.delete(id);
return ResponseEntity.noContent().build();
}
The ResponseEntity is used to return a entity declare in <>, to return a specific code, use the method .status() with enum HttpStatus, and a body use .body().
The method create will return a code CREATED = 201 and a entity Product.
The method findById and update return OK = 200 with the product, but if not find a Product with that ID, the return is NOT_FOUND.
The method findAll return OK if exist some value, else return NO_CONTENT = 204.
The method delete always return NO_CONTENT because don't have a product, but like the findById and update when not find a product return NOT_FOUND.
Testing
In Postman do this request to see the different codes.
Now when the POST localhost:8080/api/product is requested, the code return is 201 Created.
When the GET/PUT localhost:8080/api/product/1 is requested, the response code is 200 OK, but if put a ID that don't exist the return is 404 NOT FOUND.
The GET localhost:8080/api/product now return 200 OK when find some product, but if doesn't exist anyone, return 204 No Content.
To finish the DELETE in localhost:8080/api/product/1 return 204 No Content.
Noticed that when a custom exception error ResourceNotFoundException is returned, a return comes as created in the record class ExceptionResponse.
I don't do all possible return because they are like in class ProductController explanation, but feel free to test all.
Conclusion
In this post, we create the global class with @ControllerAdvice annotation to deal with all expectations.
Also improving the service and controller class to use ResponseEntity.
Next Step
In the next step we will create validations in the body of the request received.
Top comments (0)