I am trying to improve my Spring MVC application to use a global exception handler to catch various persistence exceptions across all my controllers. This is the controller code that runs when the user tries to save a new StrengthUnit object for example. All the validations work perfectly fine and the form is correctly returned with an error message underneath the name field when the PersistenceException is thrown. The resulting page also correctly contains the strengthUnit attribute and is able to bind the field (this entity just has a name field) back to the form :
#RequestMapping(value = {"/newStrengthUnit"}, method = RequestMethod.POST)
public String saveStrengthUnit(#Valid StrengthUnit strengthUnit, BindingResult result, ModelMap model) throws Exception
{
try
{
setPermissions(model);
if (result.hasErrors())
{
return "strengthUnitDataAccess";
}
strengthUnitService.save(strengthUnit);
session.setAttribute("successMessage", "Successfully added strength unit \"" + strengthUnit.getName() + "\"!");
}
catch (PersistenceException ex)
{
FieldError error = new FieldError("strengthUnit", "name", strengthUnit.getName(), false, null, null,
"Strength unit \"" + strengthUnit.getName() + "\" already exists!");
result.addError(error);
return "strengthUnitDataAccess";
}
return "redirect:/strengthUnits/list";
}
I am trying to use this as a starting point to incorporate the global exception handler that i built and I don't understand how that handler can be called and return the same page with the same model and binding result. I have tried something extremely ugly with a custom exception just to try and understand the mechanics and get the handler to return me the same page as before and I'm unable to get it to work.
Here is the custom exception I built :
public class EntityAlreadyPersistedException extends Exception
{
private final Object entity;
private final FieldError error;
private final String returnView;
private final ModelMap model;
private final BindingResult result;
public EntityAlreadyPersistedException(String message, Object entity, FieldError error, String returnView, ModelMap model, BindingResult result)
{
super(message);
this.entity = entity;
this.error = error;
this.returnView = returnView;
this.model = model;
this.result = result;
}
public Object getEntity()
{
return entity;
}
public FieldError getError()
{
return error;
}
public String getReturnView()
{
return returnView;
}
public ModelMap getModel()
{
return model;
}
public BindingResult getResult()
{
return result;
}
}
Here is my modified catch block in my controller's saveStrengthUnit method :
catch (PersistenceException ex)
{
FieldError error = new FieldError("strengthUnit", "name", strengthUnit.getName(), false, null, null,
"Strength unit \"" + strengthUnit.getName() + "\" already exists!");
result.addError(error);
throw new EntityAlreadyPersistedException("Strength unit \"" + strengthUnit.getName() + "\" already exists!", strengthUnit, error,
"strengthUnitDataAccess", model, result);
}
And finally the global exception handler's method to catch it :
#ExceptionHandler(EntityAlreadyPersistedException.class)
public ModelAndView handleDataIntegrityViolationException(HttpServletRequest request, Exception ex)
{
EntityAlreadyPersistedException actualException;
actualException = ((EntityAlreadyPersistedException)ex);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName(actualException.getReturnView());
modelAndView.addObject(BindingResult.MODEL_KEY_PREFIX + "strengthUnitForm", actualException.getResult());
if (actualException.getEntity() instanceof StrengthUnit)
{
modelAndView.addObject("strengthUnit", (StrengthUnit)actualException.getEntity());
}
return modelAndView;
}
This is extremely ugly and probably very foolish to an experienced Spring developer but I am not quite one (yet). This works BUT the binding result is lost and the validation errors do not appear. How can I modify this code to behave as it was before while still using the global exception handler to handle all errors?
Thanks!
If you are trying to catch the valid exception , which was throw when you are using the #Valid .And you want same handler handle that exception ,then add one more exception class in the #ExceptionHandler annotation
What the doc says
The #ExceptionHandler value can be set to an array of Exception
types. If an exception is thrown matches one of the types in the list,
then the method annotated with the matching #ExceptionHandler will be
invoked. If the annotation value is not set then the exception types
listed as method arguments are used.
The exception thrown by the #Valid annotation is MethodArgumentNotValidException , So you can add this exception on same handler method
I hope this may help you
1.you are setting the entity as strengthUnit.getName() not strengthUnit
throw new EntityAlreadyPersistedException("Strength unit \"" + strengthUnit.getName() + "\" already exists!", strengthUnit, error,
"strengthUnitDataAccess", model, result);
2.but you are checking if (actualException.getEntity() instanceof StrengthUnit
Hope it helps, Try to set the entity as strengthUnit.
Related
There is an Exception that is thrown when an user is not found in database and i'd like to handle that particular exception from the controller perspective layer in a separated method by #ExceptionHandler annotation without losing the original data sent by the user. Well, so, i'm using Sessions and my first attempt was trying to get the object back from it by HttpServletRequest but i got:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'pontoEditar' available as request attribute
The code:
#ExceptionHandler(ConversionFailException.class)
public String handleConversionFailException(HttpServletRequest request, RedirectAttributes attr) {
PontoEditarDTO ponto = (PontoEditarDTO) request.getAttribute("pontoEditar");
// I'd like to get the original object back ...
return "pontos/editar";
}
How would it be if i use a try-catch block
#PostMapping
public String editar(#ModelAttribute("pontoEditar") PontoEditarDTO ponto, HttpSession session) {
// ... simplified.
Ponto pontoConvertido = null;
try {
pontoConvertido = pontoConverter.convert(ponto);
catch (ConversionFailException ex) {
attr.addFlashAttribute("error", "User not found!");
return "redirect:/ponto/listar";
}
// ...
return "redirect:/ponto/listar";
}
Here the simplified code:
public class ConversionFailException extends RuntimeException {
public ConversionFailException(String mensagem) {
super(mensagem);
}
}
Controller with POST.
The exception happens in the POST at line with: Ponto pontoConvertido = pontoConverter.convert(ponto);
#Controller
#SessionAttributes("pontoEditar")
#RequestMapping("/ponto/editar")
public class PontoEditarController {
// ... GET Removed.
#PostMapping
public String editar(#ModelAttribute("pontoEditar") PontoEditarDTO ponto, HttpSession session) {
// ... simplified.
Ponto pontoConvertido = pontoConverter.convert(ponto);
// ...
return "redirect:/ponto/listar";
}
#ExceptionHandler(ConversionFailException.class)
public String handleConversionFailException(HttpServletRequest request, RedirectAttributes attr) {
attr.addFlashAttribute("falha", "Usuário não foi encontrado");
/* I tried but it failed, how can i get ? */
PontoEditarDTO ponto = (PontoEditarDTO) request.getAttribute("pontoEditar");
return "pontos/editar";
}
#GetMapping("pontoEditar")
public PontoEditarDTO getPontoModel() {
return new PontoEditarDTO();
}
}
You can add WebRequest (or HttpSession, etc...) as a parameter in your exception handler, it will be injected by Spring.
You can have a look at the documentation here to see what parameter can be injected by Spring when the handler is called.
I am handling negative cases like calling GET API which is actually a POST call. This gives Method Not Found Error with 405 status by Spring.
But I want my own exception so I added the following resolver:
public class HandlerExceptionResolver
implements org.springframework.web.servlet.HandlerExceptionResolver {
protected final Log logger = LogFactory.getLog(this.getClass());
#Override
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response,
Object handler,Exception exception) {
logger.trace("-------------doResolveException-------------");
System.out.println("-------------doResolveException-------------");
if(exception instanceof HttpRequestMethodNotSupportedException) {
Fault fault = new Fault();
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
String errorMessage;
try {
errorMessage = mapper.writeValueAsString(fault);
response.setStatus(405);
response.setContentType("application/json");
response.getWriter().println(errorMessage);
response.getWriter().flush();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
But this doesn't writes the JSON to response.
I don't think you're supposed to want to write your own output using that response parameter. Spring expects your method to return a ModelAndView instance, which you can populate with the data you want to send to the user (the model part) and the name of the view resource that will be used to render it. You would then also need to define a new view resource that will just generate the JSON you want, out of the data in the ModelAndView...
As for concern can you debug out the value of
errorMessage = mapper.writeValueAsString(fault);
there is better way to do this exception handling.
#ExceptionHandler(HandlerExceptionResolver.class)
public ModelAndView handleEmployeeNotFoundException(HttpServletRequest request, Exception ex){
logger.error("Requested URL="+request.getRequestURL());
logger.error("Exception Raised="+ex);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", ex);
modelAndView.addObject("url", request.getRequestURL());
modelAndView.setViewName("error");
return modelAndView;
}
You can try also if you want to use #ExceptionHandler to handle this
situation.
I use the following code to handle rest calls using Spring MVC.
#RequestMapping(value = "login", method = RequestMethod.GET)
public #ResponseBody
User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
...
return user;
}
I would like to send the client customer http codes for wrong username, wrong passwords, password changed and password expire conditions. How can I modify the existing code to send these error codes to the client?
You can use controller advice to map exception thrown within controller to some client specific data at runtime.
For example if user is not found, your controller should throw some exception (custom or existed one)
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UserNotFoundException(username); //or another exception, it's up to you
return user;
}
}
Then you should add #ControllerAdvice that will catch controller exceptions and make 'exception-to-status' mapping (pros: you will have single point of responsibility for 'exception-to-status-mapping'):
#ControllerAdvice
public class SomeExceptionResolver {
#ExceptionHandler(Exception.class)
public void resolveAndWriteException(Exception exception, HttpServletResponse response) throws IOException {
int status = ...; //you should resolve status here
response.setStatus(status); //provide resolved status to response
//set additional response properties like 'content-type', 'character encoding' etc.
//write additional error message (if needed) to response body
//for example IOUtils.write("some error message", response.getOutputStream());
}
}
Hope this helps.
One way is to add some additional classes for returning HTTP error. Your code will looks like this:
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UnauthorizedException();
return user;
}
}
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException{
}
In this case user will get 401 response status code
I hope it helps
You can return an HTTP 500 or code of your choosing (from the org.springframework.http.HttpStatus enumeration) and use a custom error to emulate something like a SOAP fault within the JSON response.
For example:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(YourTargetException.class)
#ResponseBody
Fault caughtYourTargetException(HttpServletRequest req, Exception ex) {
String code = ex.getClass().getName();
String reason = "Caught YourTargetException."
return new Fault(code, reason);
}
The Fault class could look something like this (inspired by http://www.w3.org/TR/soap12-part1/#soapfault):
/**
* A Fault is an object that can be serialized as JSON when expected errors occur.
*/
public class Fault {
#JsonProperty("faultCode")
private final String code;
#JsonProperty("faultReason")
private final String reason;
#JsonProperty("faultDetails")
private final List<String> details = new ArrayList<>();
public Fault(String code, String reason) {
this.code = code;
this.reason = reason;
}
public Fault(String code, String reason, String... detailEntries) {
this.code = code;
this.reason = reason;
details.addAll(Arrays.asList(detailEntries));
}
public String getCode() {
return code;
}
public String getReason() {
return reason;
}
/**
* Zero or more details may be associated with the fault. It carries
* additional information relative to the fault. For example, the Detail
* element information item might contain information about a message
* not containing the proper credentials, a timeout, etc.
* #return Zero or more detail entries.
*/
public Iterable<String> getDetails() {
return details;
}
#Override
public String toString() {
return String.format("Fault %s occurred. The reason is %s.", getCode(),
getReason());
}
}
You could use one of the existing SOAPFaults in Java frameworks, but I have found they don't play well in REST. Creating my own simple version turned out to be simpler.
You can define your own status code and returning objects. In your code throw custom exceptions and then define an exception handler as follows:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<MyRetObject> handleControllerError(HttpServletRequest req, MyException ex) {
LOG.warn("My error", ex);
MyRetObject errorMessage = new MyRetObject(ex.getMessage());
return ResponseEntity.status(600).body(errorMessage);
}
}
In your case replace MyExeption.class by UserNotFoundException.class and build your customer error response object and error code
My problem is that I want to create an #ExceptionHandler method that will capture all un-handled exceptions. Once captured I would like to redirect to the current page instead of specifying a separate page just to display error.
Basically how do I get the value of someview returned by somemethod and set it dynamically in the method unhandledExceptionHandler below.
#ExceptionHandler(Exception.class)
protected ModelAndView unhandledExceptionHandler(Exception ex){
System.out.println("unhandle exception here!!!");
ModelAndView mv = new ModelAndView();
mv.setViewName("currentview");
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
return mv;
}
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
abc.length();
return "someview";
}
So in JSP I can render this error message back into the current page something like that.
<c:if test="${not empty UNHANDLED_ERROR}">
<div class="messageError"> ${UNHANDLED_ERROR}</div>
</c:if>
I don't think there is way to do what you are asking for, because in the exception handler method unhandledExceptionHandler there is no way to find out what the name of the view that the handler method somemethod would have returned.
The only way is for you to introduce some sort of meta data scheme so that when you end up in the exception handler you can figure out what view to map it to. But I think this meta data scheme would be fairly complex. You can implement such a scheme by finding out what was the original url being accessed when the exception was thrown, this can be done with the code snippet below.
(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
Once you know what the original request URL you can redirect to it, maybe using flash attribute to store the fact that there was an exception and what the error is.
The main problem wit the metadata will occur when you have a handler method that select between different views something like.
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
if(someCondition) {
abc.length();
return "someview";
} else {
// do some stuff here.
return "someOtherView";
}
}
Even knowing that somemethod was the source of the error leaves you not knowing which branch in the if statement caused the exception.
I dont think you can do this without modifying all of your handler methods. However you can try to do this in a "pretty" way:
1) You can define your own annotation which will accept target view name as a parameter (e.g. #ExceptionView)
2) Next thing to do is marking your handler methods with it, e.g.:
#ExceptionView("someview")
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
abc.length();
return "someview";
}
3) After that you can do something like this in exception handler:
#ExceptionHandler(Exception.class)
protected ModelAndView unhandledExceptionHandler(Exception ex, HandlerMethod hm) {
String targetView;
if (hm != null && hm.hasMethodAnnotation(ExceptionView.class)) {
targetView = hm.getMethodAnnotation(ExceptionView.class).getValue();
} else {
targetView = "someRedirectView"; // kind of a fallback
}
ModelAndView mv = new ModelAndView();
mv.setViewName(targetView);
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
return mv;
}
Rather than sending the error on a separate page, you can you just put the error in the ModelAndView object. In your case you can just put the try/catch in your controller method and return the same view like so:
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(ModelAndView mv,HttpSession session) throws Exception {
mv.setViewName("someview");
try{
String abc = null;
abc.length();
} catch(Exception e) {
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
}
return mv;
}
So add the ModelAndView to your method and return it.
I have not tried this out, but based on the documentation here, we can get the request object in the exception handler. We may not be able to get the view linked to the URL. Getting the view from the URL, and the state/model of the view will be the tricky part.
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
Create a controller method annotated with #RequestMethod("/server-error")
Create a controller method annotated with #ExceptionHandler which will return "forward:/server-error";
I want to catch an "Error" in SpringMVC3 using annotated "#ExceptionHandler". I can catch throwable and any exception, but when I tried with "Error" it is not catching the exception. Any idea why? The code below demonstrates the problem.
#Controller
#RequestMapping("/test")
public class TestExceptionController {
static final Logger logger = Logger.getLogger(TestExceptionController.class);
#RequestMapping(method = RequestMethod.GET)
public String processData(int intValue) throws InvalidDataException {
if (intValue < 6) {
try {
throw new Exception();
} catch (Exception e) {
throw new InvalidDataException();
}
}
return "test";
}
#ExceptionHandler(InvalidDataException.class)
public ModelMap handleException(InvalidDataException ex) {
logger.debug("exception catched :" + ex);
return new ModelMap();
}
}
The above code catches, but below code is not catching. Why is it not catching the error?
#Controller
#RequestMapping("/test")
public class TestExceptionController {
static final Logger logger = Logger.getLogger(TestExceptionController.class);
#RequestMapping(method = RequestMethod.GET)
public String processData(int intValue) throws Error{
if (intValue < 6) {
try {
throw new Exception();
} catch (Exception e) {
throw new Error();
}
}
return "test";
}
#ExceptionHandler(Error.class)
public ModelMap handleException(Error ex) {
logger.debug("exception catched :" + ex);
return new ModelMap();
}
}
Actually I looked into the source of spring DispatcherServlet and in line 809 it explains why Error cannot be handled
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
The code is the part where spring processess the ExceptionResolvers may it be annotation or bean based. You can see that Spring only cathces Exception not Throwable. Error is not a subclass of Exception but throwable so you wont be able to handle it with Spring this way. On a related note the annotation is also called #ExceptionHandler so it kind of implies that it wont work with Errors.
From the Error javadocs:
An Error is a subclass of Throwable that indicates serious problems
that a reasonable application should not try to catch. Most such
errors are abnormal conditions. The ThreadDeath error, though a
"normal" condition, is also a subclass of Error because most
applications should not try to catch it.
Catching an Error (or any other Throwable that is not a subclass of Exception) is a bad idea. Spring is doing the right thing by not supporting it.
I also crashed into this, from the code pasted by #peter-szanto seems there is no possibility to handle java.lang.Error with a Spring-registered handler. My use case would be to handle error with a localized page and also to log the error. My workaround is to use web.xml with error-page/error-code 500 defined and as error-page/location a Spring-handled controller (not a JSP) in order to get localization work. Downside is that when controller code runs there is no authentication for the current user. This catches also things Spring could not possibly handle like a wrong URI not mapped to Spring.
For me the below works with Spring 3.0.5 MVC
#ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException{
public NotFoundException() {
super();
}
}
#ExceptionHandler
#RequestMapping(method = RequestMethod.GET, value = "employee/{id}",
headers = "Accept=application/json,application/x-protobuf,application/xml")
public #ResponseBody method(xxxx)
{
..
throw new NotFoundException
...
}
didnt meant to handle Exception instead of Error? Error is very ralely used in Java and has a very few implementing classes.