I have a ControllerExceptionHandler with ControllerAdvise and when in application is thrown generalException or customException then i except to catch it in this ControllerExceptionHandler . But it doesn't happen. It looks very simple, read many sites , but it's not triggering.
Don't know where is the problem.
It's ControllerExceptionHandler.class
#ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class);
#ExceptionHandler(Exception.class)
public final ResponseEntity<?> handleGeneralErrors(Exception e, UserPrincipal userPrincipal) {
if (e instanceof ClientAbortException) {
LOGGER.error("REST client abort: {}", e.getMessage());
} else {
LOGGER.error("REST controller error.", e);
}
//somelogic
return new ResponseEntity<>(responseObject, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(MyCustomException.class)
public final ResponseEntity<?> handleCustomErrors(MyCustomException e, UserPrincipal userPrincipal) {
LOGGER.error("MyCustomException error.", e);
return new ResponseEntity<>(
responseObject,
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Controller.class
#RestController
#RequestMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
public class Controller {
private static final Logger LOGGER = LoggerFactory.getLogger(Controller .class);
#Autowired
private SomeLogicClass someLogicClass;
#GetMapping("/check")
#ResponseBody
public ResponseEntity<List<City>> list(UserPrincipal userPrincipal) throws Exception {
//Some logic
return ResponseEntity.ok(someLogicClass.handleRequest());
}
SomeLogicClass.class
#Service
public class SomeLogicClass{
public void handleRequest() throws Exception {
//some logic
} catch (Exception ex) {
throw new MyCustomException();
}
}
}
So, when the request is reaching SomeLogicClass, then some Exception(for example NPE) is thrown and then i want to throw in catch myCustomException. I expect that it will go to ControllerAdvise, but nothing and i see error 500.
Something is missing here or what? Controller and ControllerAdvise are located in some package.
I also tried to add the package
#ControllerAdvice("my.package.controller")
Found what caused that it skipped ControllerAdvise. The problem was UserPrinciple object, that is actually my custom class. Any custom class that i put additionally in input argument was a problem. I don't know the reason for this , but implemented without UserPrinciple.class.
Related
I am using Spring Boot 3 to develop a simple Rest API and want to handle exceptions using #RestControllerAdvice, but my code is throwing a 500 error even if the code in #exceptionHandler is throwing a 404 error code. I verified in debugging that execution reaches the ExceptionHandler method, but the default exception is still thrown.
Below is sample code for:
#RestControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseStatus(code = HttpStatus.NOT_FOUND)
public Messsage resourceNotFoundException(ResourceNotFoundException exception, WebRequest request) {
return new Messsage(HttpStatus.NOT_FOUND.value(), exception.getMessage(), LocalDateTime.now());
}
#ExceptionHandler(Exception.class)
#ResponseStatus(code = HttpStatus.NOT_FOUND)
public Messsage globalException(Exception exception, WebRequest request) {
return new Messsage(HttpStatus.NOT_FOUND.value(), exception.getMessage(), LocalDateTime.now());
}
}
Try to remove ResponseEntityExceptionHandler .
which some ExceptionHandler has defined and it may catch exception before you custom exceptionHandler.
I had made some unit test here is code sample;
Application
#SpringBootApplication
public class AccountApplication {
public static void main(String[] args) {
SpringApplication.run(AccountApplication.class);
}
}
Controller
/**
* #author pengpeng
* #description
* #date 2023/2/13
*/
#RequestMapping
#RestController
public class AccountController {
#GetMapping("/test/{args}")
public String testError(#PathVariable ("args") String args) throws IllegalAccessException {
if (args .equals("ill")){
throw new IllegalAccessException();
} else if (args.equals("exception")) {
throw new RuntimeException();
}else {
return "success";
}
}
}
exceptionHandler
/**
* #author pengpeng
* #description
* #date 2023/2/13
*/
#RestControllerAdvice
public class ControllerException extends ResponseEntityResultHandler {
public ControllerException(List<HttpMessageWriter<?>> writers, RequestedContentTypeResolver resolver) {
super(writers, resolver);
}
public ControllerException(List<HttpMessageWriter<?>> writers, RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) {
super(writers, resolver, registry);
}
#ExceptionHandler
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String illegalFail(IllegalAccessException exception){
return "illegalFail";
}
#ExceptionHandler
#ResponseStatus(HttpStatus.BAD_GATEWAY)
public String fail(Exception exception){
return "fail";
}
}
after application start use the default application port to initiates HTTP call
http
127.0.0.1/test/ill
127.0.0.1/test/exception
127.0.0.1/test/success
It is likely that another exception is being thrown inside the exception handler method which is leading to the 500 error. To troubleshoot this, you can add additional logging to your exception handler method to see what is causing the 500 error. it is better to use try and catch block in resourceNotFoundException and globalException methods.
I want to let HandlerExceptionResolver resolve any Exceptions that I don't explicit catch via #ExceptionHandler annotation.
Anyways, I want to apply specific logic on those exceptions. Eg send a mail notification or log additionally. I can achieve this by adding a #ExceptionHandler(Exception.class) catch as follows:
#RestControllerAdvice
public MyExceptionHandler {
#ExceptionHandler(IOException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Object io(HttpServletRequest req, Exception e) {
return ...
}
#ExceptionHandler(Exception.class)
public Object exception(HttpServletRequest req, Exception e) {
MailService.send();
Logger.logInSpecificWay();
//TODO how to continue in the "normal" spring way with HandlerExceptionResolver?
}
}
Problem: if I add #ExceptionHandler(Exception.class) like that, I can catch those unhandled exceptions.
BUT I cannot let spring continue the normal workflow with HandlerExceptionResolver to create the response ModelAndView and set a HTTP STATUS code automatically.
Eg if someone tries a POST on a GET method, spring by default would return a 405 Method not allowed. But with an #ExceptionHandler(Exception.class) I would swallow this standard handling of spring...
So how can I keep the default HandlerExceptionResolver, but still apply my custom logic?
To provide a complete solution: it works just by extending ResponseEntityExceptionHandler, as that handles all the spring-mvc errors.
And the ones not handled can then be caught using #ExceptionHandler(Exception.class).
#RestControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
MailService.send();
Logger.logInSpecificWay();
return ... custom exception
}
}
Well, I was facing the same problem some time back and have tried several ways like extending ResponseEntityExceptionHandler but all them were solving some problems but creating other ones.
Then I have decided to go with a custom solution which was also allowing me to send additional information and I have written below code
#RestControllerAdvice
public class MyExceptionHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#ExceptionHandler(NumberFormatException.class)
public ResponseEntity<Object> handleNumberFormatException(NumberFormatException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, "Please enter a valid value"), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, ex.getMessage()), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex) {
return new ResponseEntity<>(getBody(FORBIDDEN, ex, ex.getMessage()), new HttpHeaders(), FORBIDDEN);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
return new ResponseEntity<>(getBody(INTERNAL_SERVER_ERROR, ex, "Something Went Wrong"), new HttpHeaders(), INTERNAL_SERVER_ERROR);
}
public Map<String, Object> getBody(HttpStatus status, Exception ex, String message) {
log.error(message, ex);
Map<String, Object> body = new LinkedHashMap<>();
body.put("message", message);
body.put("timestamp", new Date());
body.put("status", status.value());
body.put("error", status.getReasonPhrase());
body.put("exception", ex.toString());
Throwable cause = ex.getCause();
if (cause != null) {
body.put("exceptionCause", ex.getCause().toString());
}
return body;
}
}
Create classes for exception handling in this way
#RestControllerAdvice
public class MyExceptionHandler extends BaseExceptionHandler {
}
public class BaseExceptionHandler extends ResponseEntityExceptionHandler {
}
Here ResponseEntityExceptionHandler is provided by spring and override the several exception handler methods provided by it related to the requestMethodNotSupported,missingPathVariable,noHandlerFound,typeMismatch,asyncRequestTimeouts ....... with your own exception messages or error response objects and status codes
and have a method with #ExceptionHandler(Exception.class) in MyExceptionHandler where the thrown exception comes finally if it doesn't have a matching handler.
I had the same issue and solved it creating a implementation of the interface HandlerExceptionResolver and removing the generic #ExceptionHandler(Exception.class) from the generic handler method.
.
It works this way:
Spring will try to handle the exception calling MyExceptionHandler first, but it will fail to find a handler because the annotation was removed from the generic handler. Next it will try other implementations of the interface HandlerExceptionResolver. It will enter this generic implementation that just delegates to the original generic error handler.
After that, I need to convert the ResponseEntity response to ModelAndView using MappingJackson2JsonView because this interface expects a ModelAndView as return type.
#Component
class GenericErrorHandler(
private val errorHandler: MyExceptionHandler,
private val objectMapper: ObjectMapper
) : HandlerExceptionResolver {
override fun resolveException(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception): ModelAndView? {
// handle exception
val responseEntity = errorHandler.handleUnexpectedException(ex)
// prepare JSON view
val jsonView = MappingJackson2JsonView(objectMapper)
jsonView.setExtractValueFromSingleKeyModel(true) // prevents creating the body key in the response json
// prepare ModelAndView
val mv = ModelAndView(jsonView, mapOf("body" to responseEntity.body))
mv.status = responseEntity.statusCode
mv.view = jsonView
return mv
}
}
I'm working on a web app using Spring MVC and AngularJS, I'm creating a Rest API that returns ResponseEntities that contains JSON strings.
I want to be able when an Exception happens to return a string that contains the error cause to my view and then show this error with a modal in AngularJS, I created a Class with the #ControllerAdvice annotation and in this class I defined a method with my custom exception like this
#ControllerAdvice
public class GlobalExceptionHandlerController {
#ExceptionHandler(PersonalException.class)
public String handleCustomExceptionRazon(PersonalException ex) {
String errorMessage = "custom error";
return errorMessage;
}
}
I have the following interface
public interface ClientDAO {
public void insertCLiente(Client client) throws PersonalException
}
And in my implementation
#Override
public void insertCLiente(Client client) throws PersonalException{
//method implementation
if (searchCLiente(client.name())) {
throw new PersonalException("client aleady exists");
} else {
//method implementation
}
}
My searchClient Method
public boolean searchClient(String name) {
try {
//method implementation
} catch (DataAccessException dataAccessException) {
System.out.println("");
dataAccessException.printStackTrace();
} catch (Exception e) {
System.out.println("");
e.printStackTrace();
}
//method implementation
}
My Client Controller
#Autowired
ClientDAO clientDAO;
#RequestMapping(value = "/client/", method = RequestMethod.POST)
public ResponseEntity<Void> createClient(#RequestBody final String DTOClientData, UriComponentsBuilder ucBuilder) {
//here I parse the JSON data and create my Client object
//here I dont know how can I return the error message
clientDAO.insertClient(client);
}
My custom Exception
public class PersonalException extends Exception {
public PersonalException (String msg) {
super(msg);
}
}
I don't know un my clientController method createClient how can I return an execption of the type PersonalException that I created
//here I dont know how can I return the error message
Just throw the exception from Controller.
#RequestMapping(value = "/client/", method = RequestMethod.POST)
public ResponseEntity<Void> createClient(#RequestBody final String DTOClientData, UriComponentsBuilder ucBuilder) throws PersonalException {
You can return error message in GlobalExceptionHandlerController like this...
/**
* REST exception handlers defined at a global level for the application
**/
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { PersonalException.class })
protected ResponseEntity<RestResponse> handleUnknownException(PersonalException ex, WebRequest request) {
LOGGER.error(ex.getMessage(), ex);
return new ResponseEntity<RestResponse>(new RestResponse(Boolean.FALSE, ImmutableList.of("Exception message"), null), HttpStatus.INTERNAL_SERVER_ERROR);
}
Now, you might have noticed that we are not handling the Exception even in the Controller. Instead, we are Throwing it in the declaration hoping that somewhere we have handled this exceptional case gracefully showing the User a nice looking Toaster message.
The question may remains – Where the hell i am handling the Exception? It is handling by the #ExceptionHandler in GlobalExceptionHandlerController .
I have a question about Spring Security.
My idea is that in the case when the SM_USER header is wrong I don't want to send an uncatched exception (as the method loadUserByUsername of my class CustomUserDetailsService does).
public class CustomUserDetailsService implements UserDetailsService {...}
I want to catch it and thus redirect to the default page (with mapping my/default/page) and write a message text there like: try again please.
I already have an ExceptionResolver but it works only on controller level and not earlier.
#ControllerAdvice
public class ExceptionResolver {
#ExceptionHandler(PreAuthenticatedCredentialsNotFoundException.class)
public ResponseEntity<?> handleBindException(PreAuthenticatedCredentialsNotFoundException e) {
log.error(e.getMessage(), e);
...
return response;
}
#ExceptionHandler(UsernameNotFoundException.class)
public ResponseEntity<?> handleBindException(UsernameNotFoundException e) {
log.error(e.getMessage(), e);
...
return response;
}
}
As far as I understand I need to implement a new exception resolver for such cases, but when I try to built it in the application context, my whole programm crashes down.
#RequestMapping("/resource")
public class GlobalExceptionResolver extends AbstractHandlerExceptionResolver{
#Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse responce, Object handler, Exception exception) {
try {
responce.sendRedirect("/my/defualt/page");
} catch (IOException e) {
log.error(e);
}
ModelAndView mav = new ModelAndView();
if(exception instanceof PreAuthenticatedCredentialsNotFoundException){
mav.addObject("errorMessage","This user does not exist");
}
else if(exception instanceof UsernameNotFoundException){
mav.addObject("errorMessage","This user is too old");
}
return mav;
}
}
So, please, could you explain me how can I realize my plan in this case if spring security allows this in general?
Thank you in advance.
If you are using Spring xml you can call one bean with #PostConstruct when failed like this
<sec:form-login authentication-failure-handler-ref="afterLoginFail"
Example AfterLoginFail
public class AfterLoginFail extends SimpleUrlAuthenticationFailureHandler {
#PostConstruct
public void init() {
setDefaultFailureUrl("/login?status=failure");
}
}
Or if you use javaconfig use formLogin().failureUrl(authenticationFailureUrl) or .failureHandler()
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