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

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

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.

Why is ErrorController not throwing my custom exception class?

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

Get exception object in custom error controller

I am using spring boot and write a global exception handler use AbstractErrorController. How could i get an exception object in controller?
#Controller
public class MyCustomErrorController extends AbstractErrorController {
public MyCustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#RequestMapping("/error")
public void handleError(HttpServletRequest req, HttpServletResponse resp) {
Exception e = ...; // how to get exception here
log.error(e);
displayError(req, resp, e);
}
#Override
public String getErrorPath() {
return "/error";
}
}
You can get the exception from the HttpServletRequest as follows:
#Controller
public class MyCustomErrorController extends AbstractErrorController {
#RequestMapping("/error")
public void handleError(HttpServletRequest request) {
Exception e = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
...
}
}
An handler intercepts an Exception generated or re-thrown by a controller. It doesn't have an endpoint because it usually does it for all the controllers in your application. The Handler instructs the application server to return a specific error when a specific Exception is thrown.
Here is an example:
#ControllerAdvice // Specialization of #Component for classes that declare #ExceptionHandler, #InitBinder, or #ModelAttribute methods to be shared across multiple #Controller classes.
public class ResourceNotFoundExceptionHandler {
#ExceptionHandler(value = { ResourceNotFoundException.class })
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getLocalizedMessage(), ex);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
ResponseEntity<Object> response = new ResponseEntity<>(error, headers, HttpStatus.NOT_FOUND);
return response;
}
}
In this example ApiError is a data structure that reports the error to the UI. What this code does is intercepting the Exception "ResourceNotFoundException", create an appropriate error Data transfer object, set the response HttpStatus and headers and return the error.
you can find a different example here: https://github.com/otrebor/springbootseed-openshift/blob/master/src/main/java/com/company/example/springbootseed/core/errorhandling/handlers/
Add Exception as an extra parameter to handleError()

How to validated rest url in spring boot?

validate Rest URL in spring boot.
Requirement: If I hit the wrong URL then it should throw a custom exception.
ex. Correct URL is "/fulfillment/600747l/send_to_hub" If I hit "/api/600747l/send_to_hub_1" then it should return exception like
"404:- URL not Found.".
Right now it returning "500 : -
{
"timestamp": 1531995246549,
"status": 500,
"error": "Internal Server Error",
"message": "Invalid Request URL.",
"path": "/api/600747l/send_to_hub_1"
}"
you need to write NewClass with annotation #ControllerAdvice which will redirect all exceptions to this NewClass.
example
Your Custom Exception Class:
#Data
#AllArgsConstructor
#EqualsAndHashCode(callSuper = false)
public class IOApiException extends IOException {
private ErrorReason errorReason;
public IOApiException(String message, ErrorReason errorReason) {
super(message);
this.errorReason = errorReason;
}
}
Now the CustomExceptionHandler Class -
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
Logger logger = LoggerFactory.getLogger(this.getClass());
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = IOApiException.class)
public GlobalErrorResponse handleException(IOApiException e) {
logger.error("UNAUTHORIZED: ", e);
return new GlobalErrorResponse("URL Not Found", HttpStatus.UNAUTHORIZED.value(), e.getErrorReason());
}
//this to handle customErrorResponseClasses
public GlobalErrorResponse getErrorResponseFromGenericException(Exception ex) {
if (ex == null) {
return handleException(new Exception("INTERNAL_SERVER_ERROR"));
}
else if (ex instanceof IOApiException) {
return handleException((IOApiException) ex);
}
}
Now Your error response class:
public class GlobalErrorResponse {
private String message;
#JsonIgnore
private int statusCode;
private ErrorReason reason;
}
ErrorReason Class
public enum ErrorReason {
INTERNAL_SERVER_ERROR,
INVALID_REQUEST_PARAMETER,
INVALID_URL
}
add and register one filter who calls the GlobalExceptionHandler in exception case like this
public class ExceptionHandlerFilter implements Filter {
private final GlobalExceptionHandler globalExceptionHandler;
public ExceptionHandlerFilter(GlobalExceptionHandler globalExceptionHandler) {
this.globalExceptionHandler = globalExceptionHandler;
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception exception) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
GlobalErrorResponse errorResponse = globalExceptionHandler.getErrorResponseFromGenericException(exception);
httpResponse.setStatus(errorResponse.getStatusCode());
response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
}
}
#Override
public void destroy() {
}
}
Like this you can add as many exceptions you want.. and can handle it manually.
As per your question first of all you need to define a base url(e.g.-/api) so that any url must be handled through your controller.Now after base url as shown /api/600747l/send_to_hub_1 #PathVariable int id. This circumstance is important, because Spring documentation said that if method argument annotated with #PathVariable can’t be casted to specified type (in our case to int), it will be exposed as String. Hence it can cause a TypeMismatchException.
To handle this I will use #ExceptionHandler annotation on #Controller level. Such approach suits for this situation as no one else. I just need to make 2 changes in the Controller:
1.Add MessageSource field
2.Add exception handler method
#Autowired
private MessageSource messageSource;
...
#ExceptionHandler(TypeMismatchException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorInfo handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.bad.smartphone.id", null, locale);
errorMessage += ex.getValue();
String errorURL = req.getRequestURL().toString();
return new ErrorInfo(errorURL, errorMessage);
}
...

Resources