简体   繁体   中英

Spring Boot - Global Custom Exception Handling Mechanism using RestControllerAdvice

Am using Spring Boot for Restful Web Services.

Trying to setup a global custom exception handling mechanism which relies on @RestControllerAdvice which can handle exceptions that are known but also not known.

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.4.RELEASE</version>
</parent>

<properties>
    <java.version>1.8</java.version>
</properties>

<repositories>
    <repository>
        <id>spring-releases</id>
        <url>https://repo.spring.io/libs-release</url>
    </repository>
</repositories>

<pluginRepositories>
    <pluginRepository>
        <id>spring-releases</id>
        <url>https://repo.spring.io/libs-release</url>
    </pluginRepository>
</pluginRepositories>

<dependencies>
    <!-- Spring -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

GlobalControllerExceptionHandler:

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    private static final Logger LOG = Logger.getLogger(GlobalControllerExceptionHandler.class);

    @ExceptionHandler(value = { ConstraintViolationException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiErrorResponse constraintViolationException(ConstraintViolationException ex) {
        LOG.error(ex.getCause().toString());
        return new ApiErrorResponse(400, "Bad Request");
    }

    @ExceptionHandler(value = { NoHandlerFoundException.class })
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiErrorResponse noHandlerFoundException(Exception ex) {
        LOG.error(ex.getCause().toString());
        return new ApiErrorResponse(404, "Resource Not Found");
    }

    @ExceptionHandler(value = { Exception.class })
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ApiErrorResponse unknownException(Exception ex) {
        LOG.error(ex.getCause().toString());
        return new ApiErrorResponse(500, "Internal Server Error");
    }
}

ApiErrorResponse:

public class ApiErrorResponse {

    private int status;
    private String message;

    public ApiErrorResponse(int status, String message) {
        this.status = status;
        this.message = message;
    }

    public int getStatus() {
        return status;
    }

    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).append(status)
                                        .append(message)
                                        .toString();
    }
}

The problem with this is when I use a 3rd party library to do something, the unknown exception might be a 404 but is returned as a 500!

eg Using ElasticSearch with an unknown index (deliberately to see type of Exception):

{
  "timestamp": 1501236796640,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "org.elasticsearch.client.ResponseException",
  "message": "POST http://localhost:9200/fn3r4343/_search?pretty=true: HTTP/1.1 404 Not Found"
        {
        "error": {
            "root_cause": [
                {
                    "type": "index_not_found_exception",
                    "reason": "no such index",
                    "resource.type": "index_or_alias",
                    "resource.id": "fn3r4343",
                    "index_uuid": "_na_",
                    "index": "fn3r4343"
                }
            ],
            "type": "index_not_found_exception",
            "reason": "nosuchindex",
            "resource.type": "index_or_alias",
            "resource.id": "fn3r4343",
            "index_uuid": "_na_",
            "index": "fn3r4343"
        }
        {  "root_cause" : 
            [ 
                { 
                   "type" :"index_not_found_exception", 
                   "reason" : no such index", "resource.type" : "index_or_alias", 
                   "resource.id" : "fn3r4343",
                   "index_uuid" : "_na_",
                   "index" : "fn3r4343"
                }
            ],
            [  
                    {
                  "type" : "index_not_found_exception",
                  "reason" : "no such index", 
                  "resource.type" : "index_or_alias", 
                  "resource.id" : "fn3r4343", 
                  "index_uuid" : "_na_", 
                  "index" : "fn3r4343"  
                }, 
              "status": 404
              ]
        }
        "path": "/myapp/search"
}

As one can see, this returns as a HTTP 500 Status but in the payload its really HTTP 404!

What I am seeking is to return is this:

{ 
    "message" : "Index Not Found Exception",
    "status"  : "HTTP 404"
}

And for known HTTP 404 Exceptions:

{ 
    "message" : "Not Found",
    "status"  : "HTTP 404"
}

Is there a good practice / mechanism to use RestControllerAdvice to catch any type of Exception and customize the response into a JSON format which is readable / useful for a client using the REST API?

This post isn't really specific to Elastic Search but is seeking how by trying to use @RestControllerAdvice to handle any type of Exception put the proper response for a client app...

The underlying exception that is being raised is a org.elasticsearch.client.ResponseException (you're probably using the low-level REST client).

So in your advice you need to add a handler for that exception and return the underlying status code:

@ExceptionHandler(value = { ResponseException.class })
public ApiErrorResponse noHandlerFoundException(Exception ex) {
    LOG.error(ex.getCause().toString());
    int status = ((ResponseException) ex).getResponse().getStatusLine().getStatusCode();
    return new ApiErrorResponse(status, "<some message depending on status code>");
}

I think this below class should help:

@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {


@ExceptionHandler(value = {MyException.class})
protected ResponseEntity<Object> handleConflict(Exception exception, WebRequest request) 
{
    FinalResponse response =  new FinalResponse()//Define this Bean in a way that it contains all required paramteres to be sent in response when exception occurs
    response.setErrorCd("123");
    response.setMessage("Exception while processing.");
    if (exception instanceof MyException) 
    {
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }
    else 
    {
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM