Spring Boot - Global Custom Exception Handling Mechanism using RestControllerAdvice


#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>

            <!-- Elasticsearch -->
	<dependency>
		<groupId>org.elasticsearch</groupId>
		<artifactId>elasticsearch</artifactId>
		<version>5.5.0</version>
	</dependency>

	<dependency>
		<groupId>org.elasticsearch.client</groupId>
		<artifactId>transport</artifactId>
		<version>5.5.0</version>
	</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?

Am using ElasticSearch's RestClient instead of its API to do my calls...

Is there a way to handle an exception from the ElasticSearch that's considered a good practice for Spring Boot developers?

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


(system) #2

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.