JavaRush /Java Blog /Random EN /Exception Handling in Spring Boot Controllers
Павел
Level 11

Exception Handling in Spring Boot Controllers

Published in the Random EN group
CONTENTS OF THE ARTICLE CYCLE Hello again! It's time to dust off your keyboard. Create a spring-boot project. From the maven dependencies we need:
<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <java.version>1.8</java.version>
</properties>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.0.RELEASE</version>
    <relativePath/><!-- lookup parent from repository -->
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
Before reading further, create a project structure: Exception Handling in Spring Boot Controllers - 1 BusinessException and CustomException:
public class BusinessException extends Exception{
    public BusinessException(String message) {
        super(message);
    }
}

public class CustomException extends Exception{
    public CustomException(String message) {
        super(message);
    }
}
and the Response class
public class Response {

    private String message;

    public Response() {
    }

    public Response(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
And now, I’ll do a trick with my ears and give the floor to Alexey Kutepov, in his article Exception Handling in Spring Controllers, he will tell us how to fill these files with the correct content. Read slowly, carefully copy all the examples into your project, run and test in Postman. If in Alexey’s article the following line raised questions for you: produces = APPLICATION_JSON_VALUE , then know that it has nothing to do with exception handling, it says that by default all methods of this controller will return JSON. If necessary, in a specific method, this value can be overridden to another MediaType. If you have read it, move on. The article above discusses different options for handlers. The most flexible of them: @ControllerAdvice - it allows you to change both the code and the body of the standard response in case of an error. In addition, it allows you to handle multiple exceptions at once in one method. But that's not all, if you read further, you will get the improved @ControllerAdvice completely free of charge. Let's do some preparatory work: I want the response to display both custom and standard error messages. To do this, let's make a change to the Response class : add one more field
private String debugMessage;
Let's create an additional constructor:
public Response(String message, String debugMessage) {
    this.message = message;
    this.debugMessage = debugMessage;
}
and don't forget to create a Getter and Setter for the new field. Now to the point. Let's write another controller:
@RestController
public class Example7Controller {
    @GetMapping(value = "/testExtendsControllerAdvice")
    public ResponseEntity<?> testExtendsControllerAdvice(@RequestBody Response response) {
        return  ResponseEntity.ok(response);
    }
}
Let's test in postman: Send JSON to http://localhost:8080/testExtendsControllerAdvice
{
    "message": "message"
}
In response we will receive a status of 200 and a body
{
    "message": "message",
    "debugMessage": null
}
Now we will send obviously incorrect JSON
{
    11"message": "message"
}
In response, we will receive a status of 400 (if you have forgotten what it means, look it up on the Internet) and an empty response body. Of course, no one is happy with this, let's fight it. Previously, we created @ControllerAdvice from scratch, but in Spring Boot there is a template - ResponseEntityExceptionHandler . It already handles many exceptions, for example: NoHandlerFoundException , HttpMessageNotReadableException , MethodArgumentNotValidException and others. This class handles errors. It has a bunch of methods, the names of which are based on the principle handle + exception name. If we want to handle some basic exception, then we inherit from this class and override the desired method . Let's finalize the default advisory class
@ControllerAdvice
public class DefaultAdvice extends ResponseEntityExceptionHandler {//унаследовались от обработчика-заготовки

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Response> handleException(BusinessException e) {
        Response response = new Response(e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.OK);
    }
//Небольшое отступление: В обработчике выше, обратите внимание на HttpStatus.OK,
//он может быть и HttpStatus.BAD_REQUEST or другим, тут ограничений нет,
//попробуйте поменять статусы и потестить этот обработчик


    @Override//переопределor метод родительского класса
    protected ResponseEntity<Object> handleHttpMessageNotReadable
            (HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        Response response = new Response("Не правильный JSON",ex.getMessage());
        return new ResponseEntity<>(response, status);
    }
}
As you noticed, the handler responsible for HttpMessageNotReadableException has been overridden . This exception occurs when the body of the request coming to the controller method is unreadable - for example, incorrect JSON. The handleHttpMessageNotReadable () method is responsible for this exception . Let's make a request again with incorrect JSON: to http://localhost:8080/testExtendsControllerAdvice
{
    11"message": "message"
}
We receive a response with code 400 (Bad Request) and the body:
{
    "message": "Не правильный JSON",
    "debugMessage": "JSON parse error: Unexpected character ('1' (code 49)): was expecting double-quote to start field name; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('1' (code 49)): was expecting double-quote to start field name\n at [Source: (PushbackInputStream); line: 2, column: 6]"
}
Now the response contains not only the correct code, but also a body with informative messages. Let's check how it works with the correct JSON Request:
{
    "message": "message"
}
We received the answer:
{
    "message": "message",
    "debugMessage": null
}
To be honest, I don’t like that the response contains a field with the value null , we’ll quickly fix that now. Go to the Response class and put an annotation over the required field
@JsonInclude(JsonInclude.Include.NON_NULL)
private String debugMessage;
We restart the project, make the previous request again, and in the response we get:
{
    "message": "message"
}
Thanks to the @JsonInclude(JsonInclude.Include.NON_NULL) annotation , this field will only be included in the response if we specify it. @JsonInclude is included in the Jackson annotation library , it's very useful to know what it can do. Here are two articles to choose from: Jackson annotations. The author translated, but did not complete the translation ; Google Translit does a great job. Validation It is necessary to supplement this topic with such a concept as validation. Simply put, this is a check that the object is the object we expect. For example: if in the “Phone Directory” application we need to check the presence of telephone numbers in the database, then before going into the database, it is logical to check whether the user has entered letters instead of numbers. Three articles on validation, in increasing complexity: Bean validation in Spring Setting up DTO validation in the Spring Framework Data validation in Spring Boot We're done with the theory for today. For training, I suggest the following task: You need to implement the NightclubBouncer application. Requirements: 1) The application must accept JSON as input and write to the database. JSON example:
{
    "name": "Katy Perry"
    “status”:super star”
}
And the body of the response should contain the following inscription: Welcome + name ! 2) The application must implement the following methods: - outputting a record by id from the database to the client (Postman). - deleting a record by field: name . 3) Mapping from the dto layer to the entity and back must be implemented . 4) The application must throw a KickInTheAssException error (you need to develop it yourself) if the status field in the incoming JSON is not equal to: super star 5) The KickInTheAssException error must be handled by ControllerAdvice , and the response body must contain the message: “Don't let me see you here again! The response status should be 400. 6) Standard error EntityNotFoundException , which occurs, for example, if only Katy Perry came to the club and saved to the database with id = 1 , and you called the “display record by id” method and wanted to display the record with id = 2 , which is not in the database. This error must be handled by an overridden method of the ResponseEntityExceptionHandler class , which one is up to you to figure out yourself. The response must have the appropriate status. 7) Do the validation: a simple option - the JSON fields must not be null, or more difficult, the "name" field must consist of two words of the Latin alphabet and they both must begin with a capital letter. Invalid values ​​should throw an exception, handle it in any way, print the appropriate error code and error message: No validate. And implement all this without using the Lombok library, don't include it as a project dependency 😅
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION