Why is ErrorController not throwing my custom exception class? - spring

One of my services raises a custom exception (SearchException). The exception is defined as:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public class SearchException extends Exception {
private static final long serialVersionUID = 1L;
}
I then have a ErrorController:
#Controller
public class TVFErrorController implements ErrorController {
private static final Logger log = LoggerFactory.getLogger(TVFErrorController.class);
#RequestMapping("/error")
public String handleError(HttpServletRequest request, Exception e) {
//do something like logging
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status!=null){
log.error("An error occured: status: "+status.toString());
}
if (e != null){
log.error("Exception that occured: "+e.toString());
e.printStackTrace();
}
return "error";
}
#Override
public String getErrorPath() {
return "/error";
}
}
Most of this is working as expected the only issue is the log message describing the exception in the controller always reports a java.lang.Exception not my SearchException.

Most of this is working as expected the only issue is the log message describing the exception in the controller always reports a java.lang.Exception not my SearchException.
I suppose this is happening because of your error handler erasing exception type from SearchException to Exception when it receives it in:
public String handleError(HttpServletRequest request, Exception e)
I think you need to create explicitly error handler to your SearchException

Related

how to configure 500 error page with Spring boot 2.5.2, the default set up not working

I am relying on spring boot to handle the 500 (Internal server error). Followed various links which mentioned that I could customize my web app using the below properties
server.error.path=/error
server.error.whitelabel.enabled=false
I also wrote my custom error page controller as:
#Controller
public class GlobalErrorController implements ErrorController {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
log.error("Error occurred!");
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
// handle all the 400 error codes
if (statusCode != null && statusCode % 400 < 100) {
return "error/4xx";
} else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "error/500";
}
}
return "error/genericError";
}
}
This controller is never being called and I only see the server error page with console errors as below:
I can see see the error is clearly being thrown with /error page in console. Below is part of the error log:
2021-07-27 16:13:28.769 ERROR 22040 --- [nio-8080-exec-1]
o.a.c.c.C.[.[localhost] 175 : Exception Processing
ErrorPage[errorCode=0, location=/error]
One thing to note here is that the exception is being thrown in the filter, because I was testing the behavior of the app when one of the service goes down. But I should have still gotten the page I was expecting with /error controller.
Update:
I did some further research and found that an exception thrown in filters would not be handled by global exception handler. In order to handle the exceptions by spring's exception handler I added this filter which catches the exceptions raised by any other filters. The code is below:
#Component
public class ExceptionHandlerFilter extends OncePerRequestFilter {
private final Logger log = LoggerFactory.getLogger(getClass());
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (RuntimeException ex) {
log.error("Spring Security Filter Chain Exception:", ex);
resolver.resolveException(request, response, null, ex);
}
}
}
I also created a exception handler just for this as below:
#ControllerAdvice
public class GlobalExceptionHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#ExceptionHandler(RuntimeException.class)
public ModelAndView handleError(HttpServletRequest req, RuntimeException ex)
{
log.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView("error/500");
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
return mav;
}
}
Now the issue is that it just shows the blank page without any content. I was expecting to see my custom error page located at error/500.
This code works fine for me. Could you change your class to below code and tell the result?
error_404 and error_500 is custom html error pages.
#Controller
public class MyErrorController implements ErrorController {
#RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
switch (statusCode) {
case 404:
return "error_404";
case 500:
return "error_500";
}
}
return "error";
}
#Override
public String getErrorPath() {
return null;
}
}
server.error.whitelabel.enabled=false
spring.mvc.throw-exception-if-no-handler-found=true
server.error.path=/error

#ControllerAdvice even by setting the highest precedense for RestControllers not working as it should

I am using SpringBoot 5.
I want to catch all exception thrown from RestController and display customize message format.
I have simplified the situation like below:
The RestController
#RestController
#RequestMapping("/test")
public class TestRestController {
#Autowired
private TestService testService;
#GetMapping("/{id}")
public ResponseEntity<?> findById(#PathVariable int id) {
Test test = testService.find(id);
if(department!=null){
throw CustomException();
}
return new ResponseEntity<>(test, HttpStatus.OK);
}
}
The ControllerAdvice Exception handler:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice(annotations = RestController.class)
public class RestExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionHandler.class);
#ExceptionHandler(value= {CustomException.class})
public ResponseEntity<ErrorDetail> handleCustomException(CustomException exception,
HttpServletRequest request) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setStatus(HttpStatus.NOT_FOUND.value());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return new ResponseEntity<>(errorDetail, null, HttpStatus.NOT_FOUND);
}
}
The problem it is that RestExceptionHandler is not working, it is not catching the exception and returning the modified error message format. It seem my RestExceptionControllerClass is not overriding the GlobalExceptionHandler. I don't know why this is happening because I have marked the RestExceptionHandler with the highest precedense. I will appriciate any guidence to debug this problem.
#ControllerAdvice
public class RestExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
#ExceptionHandler(CustomException.class)
public ErrorDetail handleCustomException(CustomException exception) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return errorDetail;
}
}
Refer this link to know more about exception handling for REST API
https://www.baeldung.com/exception-handling-for-rest-with-spring
Change your RestExceptionHandler class like below
#RestControllerAdvice
public class RestExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorDetail> handleCustomException(CustomException exception) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return new ResponseEntity<>(errorDetail, null, HttpStatus.NOT_FOUND);
}
}
And you also need to extends RuntimeException in your CustomException class
The problem was that another exception was thrown before my CustomException. In the service call , there was part of code that threw an exception that i did not expect. So my RestExceptionHandler couldn't catch the exception because it didn't have a method to handle that exception and so the GlobalExceptionHandler was handling that exception. After fixing the code and made sure that the CustomExeption was thrown everything worked as it should. The RestExceptionHandler handled exception and printed the custom message.

Is there a way in spring boot to manually invoke the Exception Advice?

I have a scenario where is an already existing controller and the service throws exceptions which are handled via the #RestControllerAdvice.
Now i have a new class which i have introduced which invokes methods from the above service class in a batch mode. In my class i have to capture the exceptions or successes bundle them up and return. For any exceptions that occur i need to report the HTTP Status and the error message.
Could you let me know if there is any way this can be achieved?
You can create your own Exception class.
public class MyException extends Exception {
private int errorCode;
private String errorMessage;
public MyException(int errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
and you can create new MyException when occurring any exception and throw it. Then you get this exception in the #RestControllerAdvice class.
#RestControllerAdvice
public class ExceptionAdvice {
private ErrorCodeMapper errorCodeMapper;
#Autowired
public ExceptionAdvice(ErrorCodeMapper errorCodeMapper) {
this.errorCodeMapper = errorCodeMapper;
}
#ExceptionHandler(value = MyException.class)
public ResponseEntity handleGenericNotFoundException(MyException e) {
return new ResponseEntity(errorCodeMapper.getStatusCode(e.getErrorCode()));
}
}
and mapper class like below:
#Service
public class ErrorCodeMapper {
public static Map<Integer,HttpStatus> errorCodeMap = new HashMap<>();
public ErrorCodeMapper(){
errorCodeMap.put(100, HttpStatus.BAD_REQUEST);
errorCodeMap.put(101,HttpStatus.OK);
errorCodeMap.put(102,HttpStatus.BAD_REQUEST);
errorCodeMap.put(103,HttpStatus.BAD_REQUEST);
}
HttpStatus getStatusCode(int errorCode){
return errorCodeMap.get(errorCode);
}
}
You can more details to MyException and add the error message to the ResponseEntity.

Spring Boot: how to log httpStatus 500 error

I have created a AppErrorController that extends Boot's ErrorController in order to handle status500 errors. The example:
#Controller
public class AppErrorController implements ErrorController {
private static final Logger LOGGER = LogManager.getLogger(AppErrorController.class);
private static final String ERROR = "error";
private static final String ERROR_MESSAGE = "errorMessage";
#RequestMapping(value = "/error")
public String error(Exception e, Model model) {
LOGGER.error("500", e);
model.addAttribute(ERROR_MESSAGE, "Internal server error");
return ERROR;
}
#Override
public String getErrorPath() {
return ERROR;
}
}
I need the error to be logged. But the problem is that Exception e is always null. How to extract the actual error in order to log it?
ADDED
I have a GlobalExceptionHandler, but it never catches '500' errors
#Component
#ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LogManager.getLogger(GlobalExceptionHandler.class);
private static final String ERROR = "error";
#ExceptionHandler(Exception.class)
public String handleException(Exception e) {
LOGGER.error(e);
return ERROR;
}
}
One way to catch exception from jsp layer is to define your own error-page and point it location to you controller. Then you can extract the actual cause and do with it whatever you like.
web.xml
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error</location>
</error-page>
ErrorController
#Controller
#RequestMapping("/error")
public class ErrorController {
private final Logger log = LoggerFactory.getLogger(ErrorController.class);
#RequestMapping
public String ex(HttpServletRequest request) {
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
throwable.printStackTrace(); //print
log.error(throwable.getMessage(), throwable); // or log
// or save to db
return "error"; //and redirect to some user-friendly page
}
}

Setting the status code message of a HTTP ResponseCode thrown by an #ResponseCode annotate Exception

I am currently trying to set the message of a HTTP Status Code thrown by an #ResponseCode annotated Exception.
I have defined the exception:
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public final class BadRequestException extends IllegalArgumentException {
/**
* The serial version UID.
*/
private static final long serialVersionUID = -6121234578480284282L;
public BadRequestException() {
super();
}
public BadRequestException(String message, Throwable cause) {
super(message, cause);
}
public BadRequestException(String message) {
super(message);
}
public BadRequestException(Throwable cause) {
super(cause);
}
}
If the exception is thrown I throw it again in my #ExceptionHandler annotate method:
#ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(Exception e, HttpSession session) {
if (e instanceof BadRequestException) {
throw (BadRequestException)e;
}
return FAILURE;
}
I generally throw this exception in this way:
if (result.hasErrors()) {
throw new BadRequestException("Bad Request Message.");
}
The HTTP Status Code always returns only "HTTP Status 400 -" without a message is set.
THX!
Annotate your exception handler with the #ResponseStatus. Then created a basic error/exception view and pass the exception stack trace or whatever to that view

Resources