Description
Our need is to map all exceptions to a predefined error response model that we return to clients using our REST services. We are using Spring Boot 1.3.8 but I tried this also with Spring Boot 1.4.2 which comes alongside the Spring IO Platform Athens-SR1 release.
Currently, there is no consistent way to handle Spring MVC specific exceptions. https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html#boot-features-error-handling discusses about handling exceptions via ErrorController
, ErrorAttributes
and alternatively using Spring MVC's @ExceptionHandler
s and maybe extending ResponseEntityExceptionHandler
which is taking care of the Spring MVC specific exceptions and their mapping.
After many different solutions I ended up extending ResponseEntityExceptionHandler
but the issue is that every other exception except NoHandlerFoundException
can be handled. As suggested, one can define a property for this to change the default behavior of DispatcherServlet
spring.mvc.throw-exception-if-no-handler-found=true
However, this works only if you disable the default resource handling by defining
spring.resources.add-mappings=false
I spotted this from #3980 (comment) and it is logic that is not mentioned in the Spring Boot's reference documentation. However, this is not an option if you have static resources. We for example expose our API definition using Swagger and that stops working if you disable the default resource mapping. You might be able to manually map the static resources but I would consider that a workaround.
In my opinion, the most sensible way would be to let spring.mvc.throw-exception-if-no-handler-found=true
to throw NoHandlerFoundException
in all cases if there is handler specified. That's what the property tells anyways. If this is not possible, I hope that there would be some other way than to define your own ErrorController
or ErrorAttributes
beans as they don't get enough information for you to map exceptions because the original request is instead a call to /error
path. And in addition, having multiple components responsible for the error handling logic doesn't sound a great idea.
Here is an application to verify this behavior:
The main application with a REST controller exposing some test endpoints
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public MyExceptionHandler exceptionHandler() {
return new MyExceptionHandler();
}
@RestController
public static class MyController {
@RequestMapping(method = RequestMethod.GET, path = "/fail")
public String get() {
throw new IllegalArgumentException();
}
@RequestMapping(method = RequestMethod.GET, path = "/get-with-param")
public String getWithMandatoryParameter(@RequestParam(required = true) String param) {
throw new IllegalArgumentException();
}
}
}
The custom exception handler:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
/**
* For handling Spring MVC exceptions and making sure we are forwards compatible.
*/
@Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex,
Object body,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
return new ResponseEntity<Object>(
new ErrorResponse("my_error_code", "My message"),
headers,
status);
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ErrorResponse handleIllegalArgumentException(
HttpServletRequest request,
HttpServletResponse response,
Exception exception) {
response.setStatus(HttpURLConnection.HTTP_BAD_REQUEST);
return new ErrorResponse("invalid_request", "Failed");
}
public static class ErrorResponse {
private final String error;
private final String message;
public ErrorResponse(String error, String message) {
this.error = error;
this.message = message;
}
public String getError() {
return error;
}
public String getMessage() {
return message;
}
}
}
Tests:
@RunWith(SpringJUnit4ClassRunner.class)
@WebIntegrationTest
@SpringApplicationConfiguration(Main.class)
public class MyExceptionHandlerIT {
@Test
public void testNoHandlerFoundException() {
given()
.accept(ContentType.JSON)
.when()
.log().all()
.get("/not-found")
.then()
.log().all()
.assertThat()
.statusCode(HttpURLConnection.HTTP_NOT_FOUND)
.body("error", Matchers.equalTo("my_error_code"))
.body("message", Matchers.equalTo("My message"));
}
@Test
public void testMissingMandatoryParameter() {
given()
.accept(ContentType.JSON)
.when()
.log().all()
.get("/get-with-param")
.then()
.log().all()
.assertThat()
.statusCode(HttpURLConnection.HTTP_BAD_REQUEST)
.body("error", Matchers.equalTo("my_error_code"))
.body("message", Matchers.equalTo("My message"));
}
@Test
public void testIllegalArgumentException() {
given()
.accept(ContentType.JSON)
.when()
.log().all()
.get("/fail")
.then()
.log().all()
.assertThat()
.statusCode(HttpURLConnection.HTTP_BAD_REQUEST)
.body("error", Matchers.equalTo("invalid_request"))
.body("message", Matchers.equalTo("Failed"));
}
}
testNoHandlerFoundException()
fails but other pass as expected even though I have spring.mvc.throw-exception-if-no-handler-found
set as true
.
As a summary, it would be really great if it would be possible to handle all exceptions in a consistent manner for example by extending the ResponseEntityExceptionHandler
and adding your own exception handlers there to complement the Spring MVC exception handlers coming from the base class.
Edit: Attaching a test project for your convenience: error-handling-test.zip