Saturday, April 18, 2020

Spring REST Exception Handling Example

Learn to handle exceptions (request validation, bad data or other request processing errors) in REST APIs created with Spring REST module. We will be looking at a approach using @ControllerAdvice and @ExceptionHandler.
To handle REST exceptions globally with @ControllerAdvice, we need to follow following steps.

1. Create handler with @ControllerAdvice and @ExceptionHandler

  • @ControllerAdvice annotation is specialization of @Component annotation and it’s methods (annotated with @ExceptionHandler) are shared across multiple @Controller classes, globally.
  • Classes with @ControllerAdvice are auto-detected via classpath scanning.
  • Use selectors annotations()basePackageClasses(), and basePackages() to define a more narrow subset of targeted controllers.
  • We can apply OR operator in selector i.e. a given method would be executed if any one of given exception is encountered.
Please note that ResponseEntityExceptionHandler is a convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods.
CustomExceptionHandler.java
import java.util.ArrayList;
import java.util.List;
 
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.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
 
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
{
    private String INCORRECT_REQUEST = "INCORRECT_REQUEST";
    private String BAD_REQUEST = "BAD_REQUEST";
     
    @ExceptionHandler(RecordNotFoundException.class)
    public final ResponseEntity<ErrorResponse> handleUserNotFoundException
                        (RecordNotFoundException ex, WebRequest request)
    {
        List<String> details = new ArrayList<>();
        details.add(ex.getLocalizedMessage());
        ErrorResponse error = new ErrorResponse(INCORRECT_REQUEST, details);
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
     
    @ExceptionHandler(MissingHeaderInfoException.class)
    public final ResponseEntity<ErrorResponse> handleInvalidTraceIdException
                        (MissingHeaderInfoException ex, WebRequest request) {
        List<String> details = new ArrayList<>();
        details.add(ex.getLocalizedMessage());
        ErrorResponse error = new ErrorResponse(BAD_REQUEST, details);
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }
}

2. Create exception model classes

We need to identify the business exception usecases and denote them with exception classes. These classes will extend the RuntimeException class. Also feel free to create more representations of error responses, as per requirements.
MissingHeaderInfoException.java
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
 
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class MissingHeaderInfoException extends RuntimeException
{
    private static final long serialVersionUID = 1L;
 
    public MissingHeaderInfoException(String message) {
        super(message);
    }
}
RecordNotFoundException.java
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
 
@ResponseStatus(HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends RuntimeException
{
    private static final long serialVersionUID = 1L;
 
    public RecordNotFoundException(String message) {
        super(message);
    }
}
ErrorResponse.java
import java.util.List;
 
public class ErrorResponse
{
    public ErrorResponse(String message, List<String> details) {
        super();
        this.message = message;
        this.details = details;
    }
  
    private String message;
    private List<String> details;
 
    //getters and setters
}

3. Configure view resolver

If not done already, we need to configure the view resolver to convert the exception messages to XML or JSON form.
In Spring boot, this configuration is done automatically. Without spring boot, we need to do it like below.
Important: Make sure we you have mvc:annotation-driven configuration enabled or used @EnableWebMvc annotation.
rest-servlet.xml
    xsi:schemaLocation="http://www.springframework.org/schema/beans
 
    <mvc:annotation-driven />
 
    <context:component-scan base-package="com.howtodoinjava.demo" />
     
    <mvc:view-resolvers>
        <mvc:content-negotiation>
            <mvc:default-views>
                <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
            </mvc:default-views>
        </mvc:content-negotiation>
    </mvc:view-resolvers>
     
    <!-- JPA Config -->
</beans>

4. REST controller changes

From rest controller handler method, we need to throw the exception which we want to convert and send as response to API consumer. In this case, we are sending RecordNotFoundException in case an employee is searched by id and it does not exist in the database.
EmployeeRESTController.java
@GetMapping("/employees/{id}")
Employee getEmployeeById(@PathVariable Long id)
{
    return repository.findById(id)
            .orElseThrow(() -> new RecordNotFoundException("Employee id '" + id + "' does no exist"));
}

5. Spring REST Exception Handling Demo

Try to get an employee by id where id does not exist in database.
API Request
HTTP GET : http://localhost:8080/SpringRestExample/api/rest/employee-management/employees/101
API Response
{
    "message": "INCORRECT_REQUEST",
    "details": [
        "Employee id '101' does no exist"
    ],
}
Drop me your questions related exception handling in spring rest apis.
Happy Learning !!

No comments:

Post a Comment

How to DROP SEQUENCE in Oracle?

  Oracle  DROP SEQUENCE   overview The  DROP SEQUENCE  the statement allows you to remove a sequence from the database. Here is the basic sy...