I have created a controller advice class to return JSON error responses.
It does not respond with JSON when I get a HttpMessageNotReadableException, however I still get a "Resolved [org.springframework.http.converter.HttpMessageNotReadableException:..." log in my terminal.
My exception handler class:
#ControllerAdvice
public class PosterExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
RuntimeException.class,
})
protected ResponseEntity<ExceptionResponse> internalServerErrorHandler(RuntimeException e) {
return this.defaultResponseHandler(e, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(value = {
UserNotFoundException.class,
UserExistsException.class,
InvalidTokenException.class
})
public ResponseEntity<ExceptionResponse> badRequestResponseHandler(RuntimeException e) {
return this.defaultResponseHandler(e, HttpStatus.BAD_REQUEST);
}
public ResponseEntity<ExceptionResponse> defaultResponseHandler(RuntimeException e, HttpStatus status) {
var resp = new ExceptionResponse(
status.value(),
e.getMessage(),
Instant.now()
);
return new ResponseEntity<>(resp, status);
}
}
The exception I receive is org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<java.lang.Object> com.sulayman.poster.controller.PostController.post(com.sulayman.poster.dto.PostRequestDto
I expected the application to return a JSON response with the error
Because your PosterExceptionHandler extends by ResponseEntityExceptionHandler, which intercept HttpMessageNotReadableException
else if (ex instanceof HttpMessageNotReadableException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
}
If you want to handle the HttpMessageNotReadableException themselves, you need delete extend ResponseEntityExceptionHandler.
Related
I am unable to capture exceptions in controller advice and log those exceptions. The exceptions are extended to ResponseStatusException. I tried this out but it didnt work.
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandler.class);
#Override
protected ResponseEntity<Object> handleExceptionInternal(
#NonNull Exception ex,
#Nullable Object body,
#NonNull HttpHeaders headers,
HttpStatus status,
#NonNull WebRequest request) {
if (status.is5xxServerError()) {
LOGGER.error("An exception occurred, which will cause a {} response", status, ex);
} else if (status.is4xxClientError()) {
LOGGER.warn("An exception occured, which will cause a {} response", status, ex);
} else {
LOGGER.debug("An exception occured, which will cause a {} response", status, ex);
}
return super.handleExceptionInternal(ex, body, headers, status, request);
}
}
How could I achieve this requirement?
I recommend not extending from ResponseEntityExceptionHandler as a first test.
Create an exception handler by doing something like this:
#ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<Object> testHandler(Exception ex, WebRequest request) {
return new ResponseEntity<>("whatever", HttpStatus.NOT_FOUND);
}
I suspect that you are not telling spring to handle this specific exception via:
#ExceptionHandler(ResponseStatusException.class)
I created a REST application and added a class to handle exceptions:
#RestControllerAdvice(basePackages = "com.foxminded.university.api.controller")
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String API_UNHANDLED_EXCEPTION = "REST API reached unhandled exception: %s";
private static final HttpStatus internalServerError = HttpStatus.INTERNAL_SERVER_ERROR;
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handle(Exception e) {
ExceptionDetail exceptionDetail = new ExceptionDetail(
String.format(API_UNHANDLED_EXCEPTION, e.getMessage()),
internalServerError,
ZonedDateTime.now(ZoneId.systemDefault()));
return new ResponseEntity<Object>(exceptionDetail, internalServerError);
}
#Override
public ResponseEntity<Object> handleTypeMismatch(
TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ExceptionDetail exceptionDetail = new ExceptionDetail(
String.format(API_UNHANDLED_EXCEPTION, ex.getMessage()),
internalServerError,
ZonedDateTime.now(ZoneId.systemDefault()));
return new ResponseEntity<Object>(exceptionDetail, internalServerError);
}
#Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers,
HttpStatus status, WebRequest request) {
ExceptionDetail exceptionDetail = new ExceptionDetail(
String.format(API_UNHANDLED_EXCEPTION, e.getMessage()),
internalServerError,
ZonedDateTime.now(ZoneId.systemDefault()),
e.getBindingResult().getFieldErrors());
return new ResponseEntity<Object>(exceptionDetail, internalServerError);
}
}
When I send the wrong post request via postman http://localhost:8080/api/groups/jkjk instead of http://localhost:8080/api/groups
It throws me an exception that I can't catch initializing when debugging, neither in the ApiGlobalExceptionHandler class nor in the ResponseEntityExceptionHandler class:
{
"timestamp": 1604171144423,
"status": 405,
"error": "Method Not Allowed",
"message": "",
"path": "/api/groups/jkjk"
}
All other exceptions I can catch. How do I catch this exception to add custom handling?
You just need to add a new method to with MethodNotAllowedException in its signature.
#ExceptionHandler(value = MethodNotAllowedException.class)
public ResponseEntity<Object> handleMethodNotAllowedExceptionException(MethodNotAllowedException ex) {
return buildResponseEntity(HttpStatus.METHOD_NOT_ALLOWED, null, null, ex.getMessage(), null);
}
private ResponseEntity<Object> buildResponseEntity(HttpStatus status, HttpHeaders headers, Integer internalCode, String message, List<Object> errors) {
ResponseBase response = new ResponseBase() //A generic ResponseBase class
.success(false)
.message(message)
.resultCode(internalCode != null ? internalCode : status.value())
.errors(errors != null
? errors.stream().filter(Objects::nonNull).map(Objects::toString).collect(Collectors.toList())
: null);
return new ResponseEntity<>((Object) response, headers, status);
}
You can customize the buildResponseEntityas you please.
UPDATED
I revisited my answer since it didn't meet your requirement. So, it follows like this:
I send post request for a method that accepts GET. This will fire Request method 'POST' not supported as printed below.
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.Looking up handler method for path /v1/user/profile/1
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
org.springframework.beans.factory.support.DefaultListableBeanFactory.Returning cached instance of singleton bean 'ethGlobalExceptionHandler'
In this case, there is no need to add
#ExceptionHandler(value = HttpRequestMethodNotSupportedException.class).
In fact if you do so, the following error will be thrown (since it is already handled),
java.lang.IllegalStateException: Ambiguous #ExceptionHandler method mapped for [class org.springframework.web.HttpRequestMethodNotSupportedException]:....
So, the solution will be:
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return buildResponseEntity(HttpStatus.METHOD_NOT_ALLOWED, headers, null, ex.getMessage(), Arrays.asList(""));
}
I found the explanation here Custom handling for 405 error with Spring Web MVC
It says The reason your #ExceptionHandler annotated method never catches your exception is because these ExceptionHandler annotated methods are invoked after a successful Spring controller handler mapping is found. However, your exception is raised before that.
And the solution is to extends not from ResponseEntityExceptionHandler class, but from DefaultHandlerExceptionResolver and override it handleHttpRequestMethodNotSupported method.
I am trying to customize my body error message.
My springboot version is 2.1.5.RELEASE
I want this:
{
"This should be application specific"
}
but I'm receiving this:
{
"timestamp": "2019-05-24T15:47:10.872+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Not Found (404)",
"path": "/github/gifojhuinh4w5"
}
My exception class is:
#ControllerAdvice
public class AppExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Object> handleConflict(Exception ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}
My class where exception is captured
#Controller
#EnableAutoConfiguration
public class GitHub {
#RequestMapping(value ="/github/{usuario}", produces = "application/json; charset=UTF-8")
#ResponseBody
public ResponseEntity<Object> quantidadeRepositorios(#PathVariable(value = "usuario")String usuario) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
RepositoryService service = new RepositoryService();
GitHubClient client = new GitHubClient();
Gson gson = new Gson();
client.setOAuth2Token("key");
map.put("Total",service.getRepositories(usuario).size()); // exception captured here
return new ResponseEntity<>(gson.toJson(map), HttpStatus.OK);
}
}
When exception is caught by ExceptionHandler, build a response entity and return it as below.
Create a ErrorResponseDTO object and set message to it.
public class ErrorResponseDTO {
private String errorMessage;
}
In exception handler, return that dto object.
#ExceptionHandler(Exception.class)
protected ResponseEntity<Object> handleConflict(Exception ex, WebRequest request) {
ErrorResponseDTO errorDTO = new ErrorResponseDTO();
errorDTO.setErrorMessage("This should be application specific");
return new ResponseEntity<>(errorDTO, HttpStatus.INTERNAL_SERVER_ERROR);
}
This will give you the payload, you are looking for.
{
"This should be application specific"
}
Here is a problem: I have a controller that takes an input model. Lets say
public class AppUserUpdateData {
#NotNull
#Size(min = 1, max = 50)
protected String login;
#JsonDeserialize(using = MyDateTimeDeserializer.class)
protected Date startWorkDate;
*************
other properties and methods
*************
}
The problem is when I want to restrict a down board of a date I eventually get an HTTP exception 400 without any messages despite I handle this case in my code!
here is a controller:
#RequestMapping(
value = "/users/{userId}", method = RequestMethod.PUT,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public #ResponseBody AbstractSuccessResult updateUser(#PathVariable Long userId,
#RequestBody AppUserUpdateData appUserUpdateRequest, HttpServletRequest request) {
AbstractSuccessResult response = new AbstractSuccessResult();
appUserService.updateUser(appUserUpdateRequest, userId);
return response;
}
Here is a Deserializer:
public class MyDateTimeDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException, JsonProcessingException {
try {
return DataTypeHelper.stringToDateTime(jsonParser.getText());
} catch (MyOwnWrittenException ex) {
throw ex;
}
}
}
In a DataTypeHelper.stringToDateTime are some validations that are blocking invalid date-strings.
And there is a handler for a my exception:
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ MyOwnWrittenException .class})
protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc,
WebRequest request) {
MyOwnWrittenException ex = (MyOwnWrittenException) exc;
BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
}
}
The problem is that when an exception in a MyDateTimeDeserializer has been thrown it doesn't falling into a MyExceptionHandler but I cannot understand why? What am I doing wrong?
In the response is just an empty response with a code 400(
UPD
Thanks to #Joe Doe's answer the problem has been solved. Here is my updated handler:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ MyOwnWrittenException .class})
protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc,
WebRequest request) {
MyOwnWrittenException ex = (MyOwnWrittenException) exc;
BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Throwable cause = ex.getCause();
String message = null;
if (cause instanceof JsonMappingException) {
if (cause.getCause() instanceof MyOwnWrittenException) {
return handleInvalidRequest((RuntimeException) cause.getCause(), request);
} else {
message = cause.getMessage();
}
} else {
message = ex.getMessage();
}
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(
new BasicErrorMessage(message));
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(ex, result, headers, HttpStatus.BAD_REQUEST, request);
}
}
UPD
In my project it doesn't work without annotation #Order(Ordered.HIGHEST_PRECEDENCE)
I believe that is because of number of ControllerAdvices in a project
Before updateUser in your controller gets invoked, its arguments have to be resolved. This is where HandlerMethodArgumentResolverComposite comes in, and delegates to one of pre-registered HandlerMethodArgumentResolvers - in this particular case it delegates to RequestResponseBodyMethodProcessor.
By delegating I mean calling the resolver's resolveArgument method. This method indirectly calls the deserialize method from your deserializer, which throws an exception of type MyOwnWrittenException. The problem is that this exception gets wrapped in another exception. In fact, by the time it propagates back to resolveArgument, it's of type HttpMessageNotReadableException.
So, rather than catching MyOwnWrittenException in your custom exception handler, you need to catch exceptions of type HttpMessageNotReadableException. Then, in the method that handles that case, you can check whether the "original" exception was in fact MyOwnWrittenException - you can do that by repeatedly calling the getCause method. In my case (it's probably going to be the same in yours), I needed to call getCause twice to "unwrap" the original exception (HttpMessageNotReadableException -> JsonMappingException -> MyOwnWrittenException).
Note that you can't simply substitute MyOwnWrittenException with HttpMessageNotReadableException in your exception handler since it clashes (at runtime) with another method, specifically designed to handle exceptions of the latter type, called handleHttpMessageNotReadable.
In summary, you can do something like this:
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// ex.getCause().getCause().getClass() gives MyOwnWrittenException
// the actual logic that handles the exception...
}
}
I'm using Spring boot for hosting a REST API. Instead of having the standard error response I would like to always send a JSON response even if a browser is accessing the URL and as well a custom data structure.
I can do this with #ControllerAdvice and #ExceptionHandler for custom exceptions. But I can't find any good ways of doing this for standard and handled errors like 404 and 401.
Are there any good patterns of how to do this?
For those Spring Boot 2 users who don't wanna use #EnableWebMvc
application.properties
server.error.whitelabel.enabled=false
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
ControllerAdvice
#RestControllerAdvice
public class ExceptionResolver {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public HashMap<String, String> handleNoHandlerFound(NoHandlerFoundException e, WebRequest request) {
HashMap<String, String> response = new HashMap<>();
response.put("status", "fail");
response.put("message", e.getLocalizedMessage());
return response;
}
}
Source
It is worked for me in case of #RestControllerAdvice with spring boot
spring.mvc.throw-exception-if-no-handler-found=true
server.error.whitelabel.enabled=false
spring.resources.add-mappings=false
#RestControllerAdvice
public class ErrorHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND )
public String handleNotFoundError(NoHandlerFoundException ex) {
return "path does not exists";
}
}
I've provided the sample solution on how to override response for 404 case. The solution is pretty much simple and I am posting sample code but you can find more details on the original thread: Spring Boot Rest - How to configure 404 - resource not found
First: define Controller that will process error cases and override response:
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value= HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResponse requestHandlingNoHandlerFound() {
return new ErrorResponse("custom_404", "message for 404 error code");
}
}
Second: you need to tell Spring to throw exception in case of 404 (could not resolve handler):
#SpringBootApplication
#EnableWebMvc
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Summing up all answers and comment, I think the best way to do this is-
First, tell spring boot to throw exception in case of no handler found in application.properties
spring.mvc.throw-exception-if-no-handler-found=true
Then handle NoHandlerFoundException in your application. I handle this by following way
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public void handleNotFoundError(HttpServletResponse response, NoHandlerFoundException ex) {
ErrorDto errorDto = Errors.URL_NOT_FOUND.getErrorDto();
logger.error("URL not found exception: " + ex.getRequestURL());
prepareErrorResponse(response, HttpStatus.NOT_FOUND, errorDto);
}
}
If you are using Swagger then you can view my other answer to exclude swagger URL from this exception handler
404 error is handled by DispatcherServlet. there is a property throwExceptionIfNoHandlerFound, which you can override.
In Application class you can create a new bean:
#Bean
DispatcherServlet dispatcherServlet () {
DispatcherServlet ds = new DispatcherServlet();
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
...and then catch the NoHandlerFoundException exception in
#EnableWebMvc
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorMessageResponse requestHandlingNoHandlerFound(final NoHandlerFoundException ex) {
doSomething(LOG.debug("text to log"));
}
}
You may extend the ResponseEntityExceptionHandler class, which include a lot of common exceptions in a Spring Boot Project. For example, if you wish to use a custom handler for binding exceptions, you may use the following,
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"key\":\"value\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_ACCEPTABLE, request);
}
}
An other example for the http status 404-Not Found,
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"errormessage\":\"WHATEVER YOU LIKE\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_FOUND, request);
}
}
Regarding the 404 not found exception you should configure the DispatcherServlet to throw and exception if it doesn't find any handlers, instead of the default behavior. For issues with 404, you may also read this question.
I was having the same issue but fixed it using a different method.
To return 404, 401 and other status in a custom response, you can now add the response status to the custom exception class and call it from your exception handler.
With spring utility class AnnotationUtils, you can get the status of any of the defined custom exceptions with the findAnnotation method and it will return the appropriate status using whatever annotation you defined for the exceptions including not found.
Here's my #RestControllerAdvice
#RestControllerAdvice
public class MainExceptionHandler extends Throwable{
#ExceptionHandler(BaseException.class)
ResponseEntity<ExceptionErrorResponse> exceptionHandler(GeneralMainException e)
{
ResponseStatus status = AnnotationUtils.findAnnotation(e.getClass(),ResponseStatus.class);
if(status != null)
{
return new ResponseEntity<>(new ExceptionErrorResponse(e.getCode(),e.getMessage()),status.code());
}
}
CustomParamsException to return Bad request status
#ResponseStatus(value= HttpStatus.BAD_REQUEST)
public class CustomParamsException extends BaseException {
private static final String CODE = "400";
public CustomParamsException(String message) {
super(CODE, message);
}
}
Details not found to return Not Found Status
#ResponseStatus(value= HttpStatus.NOT_FOUND)
public class DetailsNotException extends BaseException {
private static final String CODE = "400";
public DetailsNotException(String message) {
super(CODE, message);
}
}
A GeneralMainException to extend Excetion
public class GeneralMainException extends Exception {
private String code;
private String message;
public GeneralMainException (String message) {
super(message);
}
public GeneralMainException (String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
#Override
public String getMessage() {
return message;
}
}
You can decide to handle other system exceptions by including it to the controller advice.
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
ExceptionErrorResponse sysError(Exception e)
{
return new ExceptionErrorResponse(""1002", e.getMessage());
}
It seems that you need to introduce an appropriately annotated method, e.g. for unsupported media type (415) it will be:
#ExceptionHandler(MethodArgumentNotValidException)
public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
logger.error('Caught exception', e)
def response = new ExceptionResponse(
error: 'Validation error',
exception: e.class.name,
message: e.bindingResult.fieldErrors.collect { "'$it.field' $it.defaultMessage" }.join(', '),
path: req.servletPath,
status: BAD_REQUEST.value(),
timestamp: currentTimeMillis()
)
new ResponseEntity<>(response, BAD_REQUEST)
}
However it may not be possible since 401 and 404 may be thrown before they reach DispatcherServlet - in this case ControllerAdvice will not work.
You can add custom ErrorPage objects which correlate to the error-page definition in web.xml. Spring Boot provides an example...
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new MyCustomizer();
}
// ...
private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized.html"));
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/not-found.html"));
}
}
EDIT: While I think the method above will work if you make the error pages rest controllers, an even easier way would be to include a custom ErrorController like the one below...
#Bean
public ErrorController errorController(ErrorAttributes errorAttributes) {
return new CustomErrorController(errorAttributes);
}
// ...
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#Override
#RequestMapping(value = "${error.path:/error}")
#ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
ResponseEntity<Map<String, Object>> error = super.error(request);
HttpStatus statusCode = error.getStatusCode();
switch (statusCode) {
case NOT_FOUND:
return getMyCustomNotFoundResponseEntity(request);
case UNAUTHORIZED:
return getMyCustomUnauthorizedResponseEntity(request);
default:
return error;
}
}
}
Please see Spring Boot REST service exception handling. It shows how to tell the dispatcherservlet to emit exceptions for "no route found" and then how to catch those exceptions. We (the place I work) are using this in production for our REST services right now.
Starting with Spring version 5 can use class ResponseStatusException:
#GetMapping("example")
public ResponseEntity example() {
try {
throw new MyException();
} catch (MyException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "My Exception", e);
}
}
I wanted to have the same error format (json) structure across all possible error scenarios, so I just registered my own ErrorController reusing the code from AbstractErrorController:
#Controller
#RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public class ErrorController extends AbstractErrorController {
public ErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers.orderedStream().collect(Collectors.toUnmodifiableList()));
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
final var status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
return new ResponseEntity<>(getErrorAttributes(request, ErrorAttributeOptions.defaults()), status);
}
#Override
public String getErrorPath() {
return null;
}
}
with this you dont need any controller advice, all errors go to error method by default