1

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!

e.g. 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...

2 Answers 2

2

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>");
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks @Val... I am using the low-level REST client. Your solution helped a lot but how can I get the actual Exception message? By the way, we know each other from Java Ranch, cheers!
1

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);
    }
}
}

2 Comments

Thank you, but what if the MyException.class belonged to a 3rd party lib (such as Elastic Search)? Is there a generic way to obtain any type of exception throw? Also, where would I get the actual number for the response.setErrorId("123") ? I think getting the actual number was the key solution to my question. How wouId the calling RestController use this (e.g. what would be inserted for web request)?
Wrap that 3rd Party exception by adding try catch in your code and throw you own exception. Even if you don't do this step I mentioned that will be handled in ELSE part of the logic I provided but that won't be informaive. So wrapping the 3rd party exception class will help better. Also I had mentioned define your own FinalResponse class which will have fields you want to provide as an output when that particular error occurs. You should ideally define error codes and corressponding messages while responding with error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.