Why is Spring exception handler not working as expected - spring-mvc

When I use #ExceptionHandler within Controller, it's not working as expected.
Here is my code:
#Controller
public class PageController {
#RequestMapping("/page")
public ModelAndView index(ModelAndView modelAndView){
String mess = null;
mess.split(",");
modelAndView.setViewName("index");
return modelAndView;
}
#ExceptionHandler({Exception.class})
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Bad Request")
public ModelAndView handleException(Exception ex, HttpServletRequest request, HttpServletResponse response){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", ex.getMessage());
modelAndView.addObject("url", request.getRequestURL());
modelAndView.addObject("code", response.getStatus());
modelAndView.setViewName("exception");
return modelAndView;
}
}
After the application starts with debug mode, I visit http://localhost:8080/page, and handleException is running, but the view is below other than the expected excepction view. Why?

The problem is with the #ResponseStatus annotation. Have a look at the following article: http://blog.sizovs.net/spring-rest-exception-handler/. In the mid of this article the author says following:
Warning: when using this annotation on an exception class, or when setting the reason attribute of this annotation, the HttpServletResponse.sendError method will be used.
With HttpServletResponse.sendError, the response is considered complete and should not be written to any further. Furthermore, the Servlet container will typically write an HTML error page therefore making the use of a reason unsuitable for REST APIs. For such cases it is preferable to use a org.springframework.http.ResponseEntity as a return type and avoid the use of #ResponseStatus altogether.
According to a Spring article: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc, Spring MVC chains the following three resolvers in the order below:
ExceptionHandlerExceptionResolver matches uncaught exceptions against for suitable #ExceptionHandler methods on both the handler (controller) and on any controller-advices.
ResponseStatusExceptionResolver looks for uncaught exceptions annotated by #ResponseStatus (as described in Section 1)
DefaultHandlerExceptionResolver converts standard Spring exceptions and converts them
to HTTP Status Codes (I have not mentioned this above as it is internal to Spring MVC).
So the ResponseStatusExceptionResolver is triggered after the ExceptionHanlderExceptionResolver and uses the default and will display Spring's error page.
For a quick fix try to remove #ResponseStatus and you should see your custom error page in your browser.

Related

Spring Boot - MockMVC forwardedUrl using Thymeleaf

I have a basic SpringBoot app. using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have this controller:
#Controller
#RequestMapping("/deviceevent")
public class DeviceEventController {
#RequestMapping(value={ "/list"}, method = { RequestMethod.GET})
public String deviceeventList() {
return "tdk/deviceEvent/DeviceEventList";
}
}
and this other test class. Tests using Spring's MockMVC framework. This drives an MVC application in a test, as if it was running in a container,
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#WebMvcTest
public class MockMvcTests {
// Pull in the application context created by #ContextConfiguration
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
// Setup MockMVC to use our Spring Configuration
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getDeviceEventsTest() throws Exception {
this.mockMvc
.perform(get("/deviceevent/list") //
.accept(MediaType.parseMediaType("text/html;charset=UTF-8")))
.andExpect(status().isOk()) //
.andExpect(model().size(1)) //
.andExpect(forwardedUrl("tdk/deviceEvent/DeviceEventList"));
}
But I got this error in the forwarded URL. I always used this method in JSP, never in Thymeleaf, but I guess that It is the same:
java.lang.AssertionError: Forwarded URL expected:</tdk/deviceEvent/DeviceEventList> but was:<null>
Assuming a standard Thymeleaf/Spring setup, it looks like there is a misunderstanding with what the controller is doing - when the controller returns that string "tdk/deviceEvent/DeviceEventList" it is not forwarding the HTTP request somewhere, but returning a view name.
With a normal Spring-thymeleaf setup, that string corresponds to the name of a thymeleaf view that will be rendered on hitting that endpoint (I assume the controller is just serving a normal webpage - so that path probably corresponds to some file path most likely in src/main/resources - but again, this depends a lot on your spring config) - at this point the HTTP request has not been returned to the user, and Spring is still processing it - and will attempt to render the HTML view before returning to the user.
The forwarded URL is used if Spring is not rendering anything but instead returning a HTTP response to the user to forward them to another URL (which will start a different Spring request-response process) using a 301/302 mechanism.
Note the difference in the following methods:
#RequestMapping( value="/document", method=RequestMethod.GET )
public String newDocumentSettings( Model model ){
model.addAllAttributes( contentManagementService.documentToJson() );
return "pages/document-settings";
}
#RequestMapping( value="/document", method=RequestMethod.POST )
public String createNewDocument( #RequestParam String title, #RequestParam String overview, #RequestParam String tags ){
Document doc = documentService.createDocument( title, overview, tags );
return "redirect:/document/${doc.url}/1?getting-started";
}
The first renders the template at the given filepath, the second returns a redirect command to the browser to make another HTTP request to the given URL.
In any case, the forwardedUrl in your test case is because hte HTTP Response doesn't have a URL to forward to (because its returning the HTML). If you do want forwarding behaviour (e.g. you actually want to complete the response and the browser to make a second HTTP request) then you would likely need to update the controller as per example, however, if you are happy with the rendered html page, then the test is invalid (look at the Thymeleaf testing framework to see how to test templating).
Caveat: This is based on the assumption of default Spring-Boot config - if you have other config whereby that string does result in a forwarded HTTP request then this doesnt apply!
Taking a guess here, but the URL tdk/deviceEvent/DeviceEventList is probably not defined. Try replacing it with the URL associated with your context (edit as necessary):
#Test
public void getDeviceEventsTest() throws Exception {
this.mockMvc
.perform(get("/deviceevent/list")
.accept(MediaType.parseMediaType("text/html;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(model().size(1))
.andExpect(forwardedUrl("/WEB-INF/tdk/deviceEvent/DeviceEventList.html"));
}
Aside, instead of:
#RequestMapping(value={ "/list"}, method = { RequestMethod.GET})
you can use the shorthand:
#GetMapping("/list")

Spring MVC 3 is there a common place to handle exception and display error message in the same view instead of error page

I am using spring MVC 3.2.4. I have a business exception thrown from business tier and want to display an error message in the same view instead of forwarding to an error page once it is caught. I know I could put try-catch in each handler method and then return to the same view. But I really want a common place to achieve this in Spring MVC. I tried #ExceptionHandler and #ControllerAdvice, It seems not working.
Here is what I want to do,
#SomeExceptionHandlerAnnation(BusinessException.class)??
public String handleBusinessException(Model model,
BusinessException ex)
{
//convertExceptionToMessage();
//add message to model
return userView;//stay in the same view
}
The problem is how to get model of the same view. It seems Spring MVC exception handler doesn't support it. Any idea? Thanks in advance.
Try this:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleAllExceptions(Exception e) {
--do something here
return new ModelAndView();
}
}

Dynamically changing the #ResponseStatus in annotation driven Spring MVC

I am really not sure if this is feasible using Spring 3.2 MVC.
My Controller has a method declared as below:
#RequestMapping(method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public #ResponseBody List<Foo> getAll(){
return service.getAll();
}
Questions:
What is the meaning of #ResponseStatus(HttpStatus.OK) ?
Does it signifies that the method will always return a HttpStatus.OK status code.
What if an exception is thrown from the service layer?
Can I change the Response Status on occurrence of any exception?
How can I handle multiple response statuses depending on conditions in the same method?
#ResponseStatus(HttpStatus.OK) means that the request will return OK if the handling method returns normally (this annotation is redundant for this case, as the default response status is HttpStatus.OK). If the method throws an exception, the annotation does not apply; instead, the status will be determined by Spring using an exception handler.
How can I handle multiple response statuses depending on conditions in the same method?
That question has already been asked.
Can I change the Response Status on occurrence of any exception
You have two choices. If the exception class is one of your own, you could annotate the exception class with #ResponseStatus. The other choice is to provide the controller class with an exception handler, annotated with #ExceptionHandler, and have the exception handler set the response status.
If you return a ResponseEntity directly, you can set the HttpStatus in that:
// return with no body or headers
return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
If you want to return an error other than 404, HttpStatus has lots of other values to choose from.
You cannot set multiple status value for #ResponseStatus. One approach I can think of is to use #ExceptionHandler for response status which is not HttpStatus.OK
#RequestMapping(value = "login.htm", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public ModelAndView login(#ModelAttribute Login login) {
if(loginIsValidCondition) {
//process login
//.....
return new ModelAndView(...);
}
else{
throw new InvalidLoginException();
}
}
#ExceptionHandler(InvalidLoginException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ModelAndView invalidLogin() {
//handle invalid login
//.....
return new ModelAndView(...);
}

Why Spring MVC does not allow to expose Model or BindingResult to an #ExceptionHandler?

Situation
I'm trying to group the code that logs the exceptions and render a nice view in a few methods. At the moment the logic is sometime in the #RequestHandler itself (in the a catch block), othertimes is delegated to an utility class (that works but moves the logic away from the place where the exception is thrown).
Spring's #ExceptionHandler seemed the way to group everything in one place (the controller itself or a parent) and get rid of some code (no need to put logic in the try-catch and no need for an utility class)... until I realized that an #ExceptionHandler methods won't have the ModelMap or BindingResult parameters autowired. Currently those objects are used to render the view with a sensible error message and we want to log some information contained in these objects as well.
Question
Why Spring doesn't not support method arguments such ModelMap or BindingResult for the #ExceptionHandler? What is the rationale behind it?
Possible solution
In the Spring source code (3.0.5) the arguments for the method are resolved in the HandlerMethodInvoker.invokeHandlerMethod. A an exception thrown by the request handler is caught there and re-thrown. The #ExceptionHandler and it's parameters are resolved elsewhere. As a workaround I thought to check if the Exception implements an hypothetical "ModelAware" or "BindingResultAware" interface, and in that case set Model and BindingResult attributes before re-throwhing it.
How does it sound?
As stated before you can raise an exception wrapping a binding result object in some method of your controller:
if (bindingResult.hasErrors()) {
logBindingErrors(bindingResult);
//return "users/create";
// Exception handling happens later in this controller
throw new BindingErrorsException("MVC binding errors", userForm, bindingResult);
}
With your exception defined as illustrated here:
public class BindingErrorsException extends RuntimeException {
private static final Logger log = LoggerFactory.getLogger(BindingErrorsException.class);
private static final long serialVersionUID = -7882202987868263849L;
private final UserForm userForm;
private final BindingResult bindingResult;
public BindingErrorsException(
final String message,
final UserForm userForm,
final BindingResult bindingResult
) {
super(message);
this.userForm = userForm;
this.bindingResult = bindingResult;
log.error(getLocalizedMessage());
}
public UserForm getUserForm() {
return userForm;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Next you just have to extract the required information from the raised then caught exception. Here assuming you have a suitable exception handler defined on your controller. It might be in a controller advice instead or even elewhere. See the Spring documentation for suitable and appropriate locations.
#ExceptionHandler(BindingErrorsException.class)
public ModelAndView bindingErrors(
final HttpServletResponse resp,
final Exception ex
) {
if(ex instanceof BindingErrorsException) {
final BindingErrorsException bex = (BindingErrorsException) ex;
final ModelAndView mav = new ModelAndView("users/create", bex.getBindingResult().getModel());
mav.addObject("user", bex.getUserForm());
return mav;
} else {
final ModelAndView mav = new ModelAndView("users/create");
return mav;
}
}
I ran into to same problem a while ago. The ModelMap or BindingResult are explicitly not listed as supported argument types in the JavaDocs of #ExceptionHandler, so this must have been intentional.
I reckon the reason behind it being that throwing exceptions in general could leave your ModelMap in an inconsistent state. So depending on your situation you might consider
Explicitly catch the exception to tell Spring MVC that you know what you're doing (you could use the Template pattern to refactor exception handling logic into one single place)
If you're in control of the exception hierarchy you could hand over the BindingResult to the exception and extract it from the exception later for rendering purposes
Not throw an exception in the first place, but use some result code (just like BeanValidation does for example)
HTH
To improve the first answer:
#ExceptionHandler(value = {MethodArgumentNotValidException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public VndErrors methodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {
List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
List<ObjectError> globalErrors = ex.getBindingResult().getGlobalErrors();
List<VndError> errors = new ArrayList<>(fieldErrors.size() + globalErrors.size());
VndError error;
for (FieldError fieldError : fieldErrors) {
error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), fieldError.getField() + ", "
+ fieldError.getDefaultMessage());
errors.add(error);
}
for (ObjectError objectError : globalErrors) {
error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), objectError.getDefaultMessage());
errors.add(error);
}
return new VndErrors(errors);
}
There is already MethodArgumentNotValidException has already a BindingResult object, and you can use it, if you don't need to create an specific exception for this purpose.
I had the same problem to "add" FunctinalException to ourthe BindingResult
To resolve it, we use aop, if the controller method throws a runtime exception (or the one you want),
the aop catch it and update the bindingresult or model (if they are args of the method).
The method has to be annoted with a specific annotation containing the error path (configurable for specific exception if necessary).
It is not the best way because developer must not forget to add args that he don't use in its method but Spring does not provide a simple system to do this need.
I've wondered this too.
In order to handle bean validation in a way that allows for a non-global error view to display any ConstraintViolationExceptions that may be thrown, I opted for a solution along the lines of what #Stefan Haberl proposed:
Explicitly catch the exception to tell Spring MVC that you know what you're doing (you could use the Template pattern to refactor exception handling logic into one single place)
I created a simple Action interface:
public interface Action {
String run();
}
And an ActionRunner class which does the work of ensuring ConstraintViolationExceptions are handled nicely (basically the messages from each ConstraintViolationException is simply added to a Set and added to the model):
public class ActionRunner {
public String handleExceptions(Model model, String input, Action action) {
try {
return action.run();
}
catch (RuntimeException rEx) {
Set<String> errors = BeanValidationUtils.getErrorMessagesIfPresent(rEx);
if (!errors.isEmpty()) {
model.addAttribute("errors", errors);
return input;
}
throw rEx;
}
}
}
Java 8 makes this pretty nice to run within the controller action method:
#RequestMapping(value = "/event/save", method = RequestMethod.POST)
public String saveEvent(Event event, Model model, RedirectAttributes redirectAttributes) {
return new ActionRunner().handleExceptions(model, "event/form", () -> {
eventRepository.save(event);
redirectAttributes.addFlashAttribute("messages", "Event saved.");
return "redirect:/events";
});
}
This is to wrap up those action methods for which I'd like to explicitly handle exceptions that could be thrown due to Bean Validation. I still have a global #ExceptionHandler but this deals with only "oh crap" exceptions.
Actually it does, just create an #ExceptionHandler method for MethodArgumentNotValidException.
That class gives you access to a BindingResult object.

Returning an error and message from a Spring controller or service

I'm migrating some servlets over to the Spring framework, using Spring MVC. Currently in each servlet we authenticate the user and if the authentication fails we do this:
if (authfailed)
{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"You are not authorized.");
return;
}
On the front end is a YUI-based application, and when an error status is returned the "failure" callback displays a dialog with the error message given above.
I know in my controller I can get the response object and call sendError, but is that the best way to handle this? sendError also throws an IOException so I'd have to catch that - a bit of annoying code to insert in every method of every controller.
I have the same problem handling exceptions - the servlets have try-catch blocks that call sendError in the catch method. I know I can mark my exception handlers with
#ExceptionHandler
#ResponseStatus(value = HttpStatus.NOT_FOUND)
but doesn't the exception handling class need to be in each controller class?
Finally, if the exception happens in a service called from a controller, does the exception bubble up to the controller or should I handle the exception in the service (thus pushing these exception handling issues into the service layer)?
This seems more difficult than it should be, but as with many things in Spring it's likely I don't understand what's going on. All I want to do is to send an error status and message back in the response!
Thanks,
Paul
It looks like you have the most of the answers in your question itself :)
To reiterate,
Have the controller like this
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request) throws NotFoundException {
String id = request.getParameter("id");
if (id == null)
throw new NotFoundException("Id not found in the request");
return "success";
}
Declare the exception class in NotFoundException.java,
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Id Not Found")
public class NotFoundException extends Exception {
public NotFoundException(String msg) {
super(msg);
}
}
This exception class need not be every controller class. Declare it as public class and import it in every required controller.
This is one way of doing it. If you like the non-spring style, declare HttpServletResponse in every controller arguments and do
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request, HttpServletResponse response) {
...
try {
response.sendError(..)
catch(..) {}
}
Or you can use views to show error message,
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request, Map<String, Object> map){
String id = request.getParameter("id");
if (id == null) {
map.put("status", HttpStatus.NOTFOUND);
map.put("reason", "Id Not Found");
return "error"
}
return "success";
}
Make sure your viewResolver is configured correctly and in the error.jsp to get the error string, you could say.
<body>
${status} ${reason}
</body>
Define error.jsp with nice css for all kind of errors you would expect.
These are not the only ways. With spring you have freedom to do anything. I have seen few ppl rendering json object for error message.
To answer your another question of if the error happens in the service called by the controller is depend on your scenario. For example you are trying to read the user store, if the user store not available error happens, I would handle there itself to read from another replica user store if one available and If I found user does not exist I would leave the exception to the controller to throw.

Resources