Everything you need to know about The REST API with Spring Boot + MySQL
By: Rasika Weragoda
Tags: Spring, Rest Api, Spring Boot, MySQL, Dependency Injection

Error Handling

So far so good 😎 But sometimes you have to handle the exception as well. In this section, I will explain how easily handle the exception that occurs in the program. Suppose you need to update(PUT) only the Member objects that were in the database. Otherwise, an object has been created(POST). So for that, when the PUT request comes in, we need to check whether that member id is already in the database. Then we have to update.

Let’s see how to handle this kind of situation. So first I create the util package to have my ErrorMeaage class. It only has the error message and the status (mostly 400)

package com.leo.leo.util;



public class ErrorMessage {

private String errorMsg;

private String status;



public ErrorMessage(String errorMsg, String status) {

this.errorMsg = errorMsg;

this.status = status;

}



public String getErrorMsg() {

return errorMsg;

}



public void setErrorMsg(String errorMsg) {

this.errorMsg = errorMsg;

}



public String getStatus() {

return status;

}



public void setStatus(String status) {

this.status = status;

}

}

We know the update scenario (by checking the member id in the database). What are we don’t have that it??? 🙄 Then we have thrown the ValidationException. That exception doesn’t sound like JSON output… Right😣. We need to have an output that says “You Member id is not available, you are not able to update it” with the status of BAD_REQUEST(400).

When we throw the exception, there needs to be a mechanism to catch that exception. Then in the catch block, we can return the ResponseEntity with that message. Spring provides us with an awesome strategy for that. Once we annotated some class with the @ControllerAdvice, all the thrown exceptions are headed to this class. we can think this is some kind of exception handling class. Inside this class, we annotated methods with @ExceptionHandler(<Exception data type>) and tell how to handle this specific Exception.

Modifying our PutMapping in MemberController class:

@PutMapping("/member")

ResponseEntity<Member> updateMember(@RequestBody Member member) throws ValidationException {

if(memberService.findById(member.getId()).isPresent()){

Member updateMember = memberService.save(member);

return new ResponseEntity(updateMember, HttpStatus.OK);

}else

throw new ValidationException("Cannot be updated, No such kind of member");

}

So here you can see if we have an Id then we update like previously otherwise we throw ValidationException. As we know all the exceptions are now headed to the @ControllerAdvice class and handled the method that is annotated by @ExceptionHandler(ValidationException.class).

I create the ExceptionController class inside the Controller package.

@ControllerAdvice

public class ExceptionController {



@ExceptionHandler(ValidationException.class)

ResponseEntity<ErrorMessage> exceptionHandler(ValidationException e){

ErrorMessage err_obj = new ErrorMessage(e.getMessage(), "400");

return new ResponseEntity(err_obj, HttpStatus.BAD_REQUEST);

}

}

since this is little unclear look the image:

Now you have an idea about what is going on here. we take the thrown exceptional message and attach to the ErrorMessage object and return the ErrorMessage ResponseEntity.😋

The Output:

JSON data validations

Suppose that your first name, sex and age need to be required. If you are not providing those things before saved then the error occurs. I know what you think…😌 it is easy right… you all have to do get each field (first name, sex, age) and check whether if they are blank or not. If they all not blank, then save the data else throw ValidationException…

But we don’t have to do this. Because we are dealing with an awesome framework. Right…

Here are some basic validations that you annotate with fields of Entity class. For more details Here

@NotNull a constrained CharSequence, Collection, Map, or Array is valid as long as it's not null, but it can be empty.

@AssertTrue check if the boolean value is true

@Min check the min value of a number

@Max check the max value of a number

@Size check the length of string or collection

@Digit check if represent the number

@Pattern regular expression

@NotEmpty a constrained CharSequence, Collection, Map, or Array is valid as long as it's not null and its size/length is greater than zero

@NotBlank a constrained String is valid as long as it's not null and the trimmed length is greater than zero

@Postive check number is positive

@Negative check number is positive

@PostiveOrZero check number is positive including 0

@NegativeOrZero check number is negative including 0

@Email check if email is valid

@Past check if data is in the past

So we can annotate our Member class’s fields. So in this case, the first name, sex and age are required. Then we apply @NotBlank annotation to those fields.

@Entity

public class Member {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private int id;

@NotBlank

private String firstName;

private String lastName;

@NotBlank

private String sex;

@NotBlank

private int age;



public int getId() {

return id;

}



public void setId(int id) {

this.id = id;

}



public String getFirstName() {

return firstName;

}



public void setFirstName(String firstName) {

this.firstName = firstName;

}



public String getLastName() {

return lastName;

}



public void setLastName(String lastName) {

this.lastName = lastName;

}



public String getSex() {

return sex;

}



public void setSex(String sex) {

this.sex = sex;

}



public int getAge() {

return age;

}



public void setAge(int age) {

this.age = age;

}

}

So at the controller, we need to say take an only valid request body or otherwise throw an exception. So we can say this by adding @Valid annotation to the request body. Eventually, this annotation throws the MethodArgumentNotValidException if the request body is not valid. 🥰 So all we have to do now define the method to handle the MethodArgumentNotValidException in the @ControllerAdvice class(ExceptionController).

Here is the updated version of MemberController class:

@RestController

public class MemberController {



@Autowired

private MemberService memberService;





@GetMapping("/members")

ResponseEntity<Iterable<Member>> getAllMember(){

return new ResponseEntity(memberService.findAll(), HttpStatus.OK);

}



@PostMapping("/member")

ResponseEntity<Member> saveMember(@Valid @RequestBody Member member){

Member saveMember = memberService.save(member);

return new ResponseEntity(saveMember, HttpStatus.CREATED);

}



@PutMapping("/member")

ResponseEntity<Member> updateMember(@Valid @RequestBody Member member) throws ValidationException {

if(memberService.findById(member.getId()).isPresent()){

Member updateMember = memberService.save(member);

return new ResponseEntity(updateMember, HttpStatus.OK);

}else

throw new ValidationException("Cannot be updated, No such kind of member");

}



@ResponseStatus(HttpStatus.NO_CONTENT)

@DeleteMapping("/member/{id}")

void deleteMember(@PathVariable Integer id){

memberService.deleteById(id);

}



@GetMapping("/member/{id}")

ResponseEntity<Optional<Member>> getMemberById(@PathVariable Integer id){

final Optional<Member> byId = memberService.findById(id);

return new ResponseEntity(byId, HttpStatus.OK);

}





}

Let’s see how to define @ExceptionHandler(MethodArgumentNotValidException.class) method in @ControllerAdvice class.

@ExceptionHandler(MethodArgumentNotValidException.class)

ResponseEntity<Stream<FieldErrorMessage>> exceptionHandler(MethodArgumentNotValidException e){

List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();

Stream<FieldErrorMessage> fieldErrorMessageStream = fieldErrors.stream().map(fieldError ->

new FieldErrorMessage(fieldError.getField(), fieldError.getDefaultMessage()));

return new ResponseEntity(fieldErrorMessageStream, HttpStatus.BAD_REQUEST);

}

So let’s figure out what this method does. First, get the fields errors of MethodArgumentNotValidException thrown by @Valid annotation. Then we map each field error with our FieldErrorMessage class that we created(I created in the util package with 2 fields named “fields” and “message” ). I will put that code snippet below. Finally, we return the ResponseEntity with a set of FieldErrorMessage streams.

The FieldErrorMessage class(inside the util package):

public class FieldErrorMessage {

private String field;

private String message;



public FieldErrorMessage(String field, String message) {

this.field = field;

this.message = message;

}



public String getField() {

return field;

}



public void setField(String field) {

this.field = field;

}



public String getMessage() {

return message;

}



public void setMessage(String message) {

this.message = message;

}

}

The output: