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

Handling Exceptions in Spring Boot Controllers

Published in the Random EN group
CONTENT OF THE ARTICLE CYCLE And hello again! It's time to dust off your keyboard. We create a spring-boot project. From 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: Handling Exceptions 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 class Response
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 feint with my ears, and give the floor to Alexey Kutepov, in his article Handling Exceptions in Spring Controllers, he will tell us how to fill these files with the correct content. Read slowly, carefully copy all the examples to your project, run and test in postman. If in Alexey’s article, the following line raised questions for you: produces = APPLICATION_JSON_VALUE , then you should 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 read, let's move on. The above article 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 on error. In addition, it allows you to handle several exceptions at once in one method. But that's not all, if you read on, you'll get the improved @ControllerAdvice for free. Let's carry out preparatory work: I want both custom and standard error messages to be displayed in the response. 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 business. 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 get status 200 and the body
{
    "message": "message",
    "debugMessage": null
}
Now we will send obviously incorrect JSON
{
    11"message": "message"
}
In response, we will receive status 400 (if you forgot what it means, look on the Internet) and an empty response body. Of course, this does not suit anyone, let's fight it. Previously, we created @ControllerAdvice from scratch, but there is a blank in Spring Boot - ResponseEntityExceptionHandler . Many exceptions are already handled in it, for example: NoHandlerFoundException , HttpMessageNotReadableException , MethodArgumentNotValidException and others. This class handles errors. It has a lot of methods, the name of which is built on the principle of handle + the name of the exception. If we want to handle some basic exception, then we inherit from this class and override the desired method. Let's finalize the default advisa 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 can see, the handler responsible for the HttpMessageNotReadableException has been overridden . This exception occurs when the body of the request coming to the controller method is unreadable - for example, invalid JSON. The handleHttpMessageNotReadable () method is responsible for this exception . Let's make a request with incorrect JSON again: to http://localhost:8080/testExtendsControllerAdvice
{
    11"message": "message"
}
We get a response with a 400 (Bad Request) code and a 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 the body with informative messages. Let's check how it works with the correct JSON Request:
{
    "message": "message"
}
Received a response:
{
    "message": "message",
    "debugMessage": null
}
To be honest, I do not like that the response has a field with a value of null , now we will quickly fix this. Go to the Response class and put an annotation over the desired field
@JsonInclude(JsonInclude.Include.NON_NULL)
private String debugMessage;
We restart the project, make the previous request again, 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 set it. @JsonInclude is part of 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 finish translating , 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 we need to check the presence of phone numbers in the database in the Phonebook application, then before climbing into the database, it is logical to check if the user entered letters instead of numbers. Three articles on validation, on complexity recurrence: Bean Validation in Spring Configuring DTO Validation in Spring Framework Data Validation in Spring Boot We've finished the theory for today. For training, I propose the following task: It is necessary to implement the application NightclubBouncer (Nightclub Bouncer). Requirements: 1) The application must accept JSON as input and write to the database. JSON example:
{
    "name": "Katy Perry"
    “status”:super star”
}
And in the response body there should be an inscription: Welcome + name ! 2) The application must implement the following methods: - displaying a record by id from the database to the client (Postman). - deleting a record by the field: name . 3) Mapping from the dto layer to the entity and back must be implemented . 4) The application should 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 should be handled by ControllerAdvice, and the body of the response should contain the message: "Don't let me see you here again!". The response status should be 400. 6) The standard error EntityNotFoundException , which occurs, for example, if only Katy Perry entered 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 the overridden class method ResponseEntityExceptionHandlerexactly how - you have to figure it out yourself. The response must have the appropriate status. 7) Validate: a simple option - the JSON fields must not be null, but more difficultly, the "name" field must consist of two words of the Latin alphabet and both of them 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, do not include it in the project dependencies 😅
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION