I am using @ControllerAdvice to do exception handling globally. but I want to return the exception in Json format if the request is ajax(xhr) and if it's not I want to show my exception as html.
so the response type is dynamic. it might be html or json. And I am using thymleaf to generate my html pages.
how can I do that in spring boot?
@ResponseBody
@ExceptionHandler(Exception.class)
public ModelAndView handle(Exception exception, WebRequest request) {
this.logException(exception, request);
var model = getErrorPageModel(exception);
return new ModelAndView("exception", model, HttpStatus.OK);
}
From the WebRequest
, you have to retrieve the necessary information to either return JSON or HTML. One possible way of doing so is by parsing the Accept
header:
List<MediaType> mediaTypes = MediaType.parseMediaTypes(request.getHeader("Accept"));
if (mediaTypes.contains(MediaType.TEXT_HTML)) {
// TODO: Implement HTML view
} else {
// TODO: Implement JSON view
}
A better alternative is to use Spring's ContentNegotiationManager
by autowiring it into your controller advice:
// TODO: Autowire a bean of `ContentNegotiationManager` into your ControllerAdvice class
List<MediaType> mediaTypes = contentNegotiationManager.resolveMediaTypes(request);
if (mediaTypes.contains(MediaType.TEXT_HTML)) {
// TODO: Implement HTML view
} else {
// TODO: Implement JSON view
}
By default, Spring Boot's ContentNegotiationManager
will do exactly the same as the original code, but this can be extended with other strategies.
Be aware that the resolveMediaTypes()
method requires a NativeWebRequest
. So you have to change the type of the request
parameter.
Now what you have to do is implement the model and view for both the HTML and JSON view.
For the HTML view, you can use a Thymeleaf template, for example error.html
:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<p><strong>Error:</strong> <span th:text="${message}"></span></p>
</body>
After that, you can return the following ModelAndView
from your exception handler:
return new ModelAndView("error", Map.of("message", ex.getMessage()));
For the JSON part, you can use the MappingJackson2JsonView
view class:
// TODO: Autowire a bean of `ObjectMapper` into your ControllerAdvice class
return new ModelAndView(new MappingJackson2JsonView(objectMapper), Map.of("message", ex.getMessage()));
This requires an ObjectMapper
, which you can autowire into your controller advice.
Everything combined you get:
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex, NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
List<MediaType> mediaTypes = contentNegotiationManager.resolveMediaTypes(request);
Map<String, String> model = Map.of("message", ex.getMessage());
if (mediaTypes.contains(MediaType.TEXT_HTML)) {
return new ModelAndView("error", model);
} else {
return new ModelAndView(new MappingJackson2JsonView(objectMapper), model);
}
}
Note: In this example I'm assuming that if the
Accept
header containstext/html
you want to return the HTML view and in every other case you want to return the JSON view. You can change this logic to whatever you want.