Spring Boot 3 Exception Handlers with @RestControllerAdvice

Spring Boot provides REST controller advice annotations for exception handlers that reduce redundant code and the typical response format. This helps the development application be easy to maintain. A rest controller advice annotation interceptor is provided for every exception defined in the Java class, assisting the developer in responding to errors in the same format as any request. This makes it easy for the front-end and back-end to communicate.

Scenario

The development team designed a web services application that creates users and needs standard exception handlers when web services encounter errors.

Discussion

The development team decided to use REST controller advice annotation and design a response format for any request that occurs as an exception.

Implementation

The developer creates a Java class and adds an annotation of the REST controller advice. They assign exceptions that need standard responses and create a typical response Java bean for the standard response format.

For example, the rest controller advice annotation

creates RESTful web services for creating users. Return a response error when an exception or validation error.

1. Create the CommonResponseBean class

    import lombok.Getter;
    import lombok.Setter;
    import java.util.List;
    
    @Getter
    @Setter
    public class CommonResponseBean {
        private long timestamp;
        private List<ErrorBean> errors;
    }

    2. Create ErrorBean class

    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class ErrorBean {
        private String errorCode;
        private String errorMessage;
    
        public ErrorBean(String errorCode, String errorMessage) {
            this.errorCode = errorCode;
            this.errorMessage = errorMessage;
        }
    }

    3. Create UserProfile

    import jakarta.validation.constraints.Min;
    import jakarta.validation.constraints.NotEmpty;
    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class UserProfile {
    
        @NotEmpty
        private String firstName;
    
        @NotEmpty
        private String lastName;
    
        private String effectiveDate;
    
        @Min(0)
        private long age;
    
        public UserProfile(String firstName,String lastName, int age, String effectiveDate){
            this.firstName = firstName;
            this.lastName = lastName;
            this.effectiveDate =  effectiveDate;
            this.age = age;
        }
    }

    4. Create a RESTful service

    import com.example.demo.bean.UserProfile;
    import jakarta.validation.Valid;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    
    @RestController
    public class CreateUser {
    
        @PostMapping(path = "/user", produces = "application/json")
        public ResponseEntity<String> create(@Valid @RequestBody UserProfile userProfile) throws ParseException {
            SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
            if(!userProfile.getEffectiveDate().isEmpty())
            df.parse(userProfile.getEffectiveDate());
            return new ResponseEntity<>("created",HttpStatus.CREATED);
        }
    }

    5. Create an exception handler

    import com.example.demo.bean.CommonResponseBean;
    import com.example.demo.bean.ErrorBean;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import java.text.ParseException;
    import java.util.ArrayList;
    import java.util.Date;
    
    @RestControllerAdvice
    public class CommonExceptionHandlers  {
    
        @ExceptionHandler(ParseException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public ResponseEntity<CommonResponseBean> handleParseException(ParseException ex) {
            // Customize the response entity
            CommonResponseBean res = new CommonResponseBean();
            res.setTimestamp(new Date().getTime());
            res.setErrors(new ArrayList<>());
            res.getErrors().add(new ErrorBean("E0001",ex.getMessage()));
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(res);
        }
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public ResponseEntity<CommonResponseBean> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
            // Customize the response entity
            CommonResponseBean res = new CommonResponseBean();
            res.setTimestamp(new Date().getTime());
            res.setErrors(new ArrayList<>());
            res.getErrors().add(new ErrorBean("E0002",ex.getMessage()));
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(res);
        }
    }

    Test case scenario

    Using Postman is an easy tool for creating test case scenarios. This suite test case will show how the REST controller advice annotation works.

    1. Test case: call web services path /user

    Request and response on Postman.

    Test result: response body from web services is “created”, and HTTP status 201 is created.

    2. Test case: call web services path /user, and the first name is empty

    Request and response on Postman.

    Test result: The response body from web services is an error, and HTTP status 400 is a bad request.

    This response contains timestamps and errors. This format is defined in “CommonResponseBean” when a MethodArgumentNotValidException occurs. The developer can modify the error message from this exception to be readable.

    3. Test case: call web services path /user, and the effective date invalid pattern

    Request and response on Postman.

    Test result: The response body from web services is an error, and HTTP status 400 is a bad request.

    Conclusion

    This response contains timestamps and errors. This is the same format as MethodArgumentNotValidException, defined in “CommonResponseBean” when a ParseException occurs.

    This test scenario will show that any exceptions put in “CommonExceptionHandlers” use a custom response error designed by the developer. This design made the exception handler easy to maintain.

    Finally

    The REST Controller advice annotation made RESTful Web Services
    Reduce redundant code, which is easy to maintain because all exceptions are contained in a single class. The developer can add various exceptions in this class and modify the error response format and error message for any exception. It is friendly for the front end to receive a response that all errors are in the same format. The front-end developer can easily organize error message formats displayed to clients.

    Leave a Comment

    Your email address will not be published. Required fields are marked *