#ExceptionHandler and validation errors - java

Hi I am wondering how to pass information to the exception handler. Say for example I doing validation. Whit #Valid. I can catch that specific exception but that does not tell me if was the first name or last name of the person that was wrong.
Maybe a custom exception with a error field attribute. If I do that is how to I through it?
#Valid have already thrown an exception if validation fails.
Can i check the bindingresult for error and throw my custom exception? How do solve this?
#RequestMapping(value = "/post", method = RequestMethod.POST)
public String post(#Valid Person person) {
System.out.println(person);
System.out.println(person2);
return "home";
}
#ExceptionHandler(Exception.class)
#ResponseBody
public List<FailureResult> handleException
(Exception re, HttpServletResponse response) {
FailureResult failureResult = new FailureResult();
failureResult.setName("name");
//wich feild failed the validation?
List<FailureResult> r = new ArrayList<FailureResult>();
r.add(failureResult);
return r;
}

Related

Spring MVC: correct exception handling

I am wondering how to bind exception handling method to url mapping method:
#Controller
public class UserController {
#Autowired
UserDao userDao;
#RequestMapping(value = "/user", method = RequestMethod.GET)
public String users(#ModelAttribute("model") ModelMap model) {
model.addAttribute("userList", userDao.getAll());
String[] b = new String[0];
String a = b[1];
return "user";
}
#ExceptionHandler(Exception.class)
public String handleAllException(Exception ex, #ModelAttribute("model") ModelMap model) {
model.addAttribute("error", "Exception happened");
return "error_screen";
}
}
I intentionally provoke java.lang.ArrayIndexOutOfBoundsException in users method. But I see that handleAllException method wasn't executed.
Question:
What have I forgotten to get done to make Exception Handling work appropriately?
Try to do somedthing like this:
#ExceptionHandler(Exception.class)
public ModelAndView handleAllException(Exception ex) {
ModelAndView model = new ModelAndView("error_screen");
model.addAttribute("error", "Exception happened");
return model;
}
Try the following
#ExceptionHandler(Exception.class) -> #ExceptionHandler({Exception.class})
The reason is it has failed with the below exception while trying to invoke the handleAllException() method:
DEBUG [http-nio-8080-exec-6] --- ExceptionHandlerExceptionResolver: Failed to invoke #ExceptionHandler method: public java.lang.String controllers.handleAllException(java.lang.Exception,org.springframework.ui.ModelMap)
java.lang.IllegalStateException: No suitable resolver for argument [1] [type=org.springframework.ui.ModelMap]
HandlerMethod details:
Change the Method as below:
#ExceptionHandler(Exception.class)
public String handleAllException(Exception ex) {
// model.addAttribute("error", String.format("Exception happened"));
return "error_screen";
}

Intercept #RequestHeader exception for missing header

I have a method in controller with has parameter for example
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader("ETag") int etag)
If there is no ETag header in request - client gets 400 (BAD_REQUEST), which is not any informative.
I need to somehow handle this exception and send my own exception to client (I use JSON for this purpose).
I know that I can intercept exception via #ExceptionHandler, but in that case all HTTP 400 requests will be handled, but I want that have missing ETag in headers.
Any ideas?
You can also achieve this by use of annotation #ControllerAdvice from spring.
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler{
/**
* Handle ServletRequestBindingException. Triggered when a 'required' request
* header parameter is missing.
*
* #param ex ServletRequestBindingException
* #param headers HttpHeaders
* #param status HttpStatus
* #param request WebRequest
* #return the ResponseEntity object
*/
#Override
protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<>(ex.getMessage(), headers, status);
}
}
The response when you access your API without the required request header is:
Missing request header 'Authorization' for method parameter of type String
Like this exception, you can customise all other exceptions.
In case Spring version is 5+ then the exact exception you need to handle is the MissingRequestHeaderException. If your global exception handler class extends ResponseEntityExceptionHandler then adding an #ExceptionHandler for ServletRequestBindingException won't work because MissingRequestHeaderException extends ServletRequestBindingException and the latter is handled inside the handleException method of the ResponseEntityExceptionHandler. If you try you're going to get Ambiguous #ExceptionHandler method mapped for ... exception.
There are two ways to achieve what you are trying
First using #RequestHeader with required false
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader(value="ETag", required=false) String ETag) {
if(ETag == null) {
// Your JSON Error Handling
} else {
// Your Processing
}
}
Second using HttpServletRequest instead of #RequestHeader
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(HttpServletRequest request) {
String ETag = request.getHeader("ETag");
if(ETag == null) {
// Your JSON Error Handling
} else {
// Your Processing
}
}
Write a method with the annotation #ExceptionHandler and use ServletRequestBindingException.class as this exception is thrown in case of missing header
For example :
#ExceptionHandler(ServletRequestBindingException.class)
public ResponseEntity<ResponseObject> handleHeaderError(){
ResponseObject responseObject=new ResponseObject();
responseObject.setStatus(Constants.ResponseStatus.FAILURE.getStatus());
responseObject.setMessage(header_missing_message);
ResponseEntity<ResponseObject> responseEntity=new ResponseEntity<ResponseObject>(responseObject, HttpStatus.BAD_REQUEST);
return responseEntity;
}
In Spring 5+ it is as simple as this. ErrorResponse is your own object to return
#RestControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(MissingRequestHeaderException.class)
public ResponseEntity<ErrorResponse> handleException(MissingRequestHeaderException ex) {
log.error("Error due to: " + ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse();
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
You should user an #ExceptionHandler method that looks if ETag header is present and takes appropriate action :
#ExceptionHandler(UnsatisfiedServletRequestParameterException.class)
public onErr400(#RequestHeader(value="ETag", required=false) String ETag,
UnsatisfiedServletRequestParameterException ex) {
if(ETag == null) {
// Ok the problem was ETag Header : give your informational message
} else {
// It is another error 400 : simply say request is incorrect or use ex
}
}
If you don't want to handle this in your request mapping, then you could create a Servlet Filter and look for the ETag header in the Filter. If it's not there, then throw the exception. This would apply to only requests that match your filter's URL mapping.
public final class MyEtagFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String etag = request.getHeader("ETag");
if(etag == null)
throw new MissingEtagHeaderException("...");
filterChain.doFilter(request, response);
}
}
You'll have to implement your own MissingEtagHeaderException, or use some other existing exception.
This is relatively simple. Declare two handler methods, one that declares the appropriate header in the #RequestMapping headers attribute and one that doesn't. Spring will take care to invoke the appropriate one based on the content of the request.
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST, headers = "ETag")
#ResponseStatus(HttpStatus.CREATED)
public void postWith(#RequestHeader("ETag") int etag) {
// has it
}
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void postWithout() {
// no dice
// custom failure response
}
You can also intercept the exception without extending ResponseEntityExceptionHandler:
#ControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(ServletRequestBindingException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex) {
// return a ResponseEntity<Object> object here.
}
}
You can add #Nullable to this request param, and in case of absence, request still enters the controller without throwing MissingRequestHeaderException, and you add manual validation to throw whatever you like in controller and handle in the ExceptionHandler.
You can create a custom exception class e.g. InvalidRequestHeaderException.java. You can customise your exception message here.
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class InvalidRequestHeaderException extends RuntimeException {
public InvalidRequestHeaderException() {
super("Invalid request header provided.");
}
}
In your controller, you can throw an exception if the header provided is invalid.
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader("ETag") int etag) {
// some code
if (!isSupportedPlatform(platform)) {
throw new InvalidRequestHeaderException();
}
// some code
}
You can then create a ValidationHandler.java to handle these exceptions.
#RestControllerAdvice
public class ValidationHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
MissingRequestHeaderException.class,
InvalidRequestHeaderException.class
})
protected ResponseEntity<Object> handleRequestHeaderException(Exception ex) {
log.error(ex.getMessage());
return ResponseEntity.badRequest().body(ErrorResponse.builder()
.status(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.reason(ex.getMessage()).build());
}
#AllArgsConstructor
#Getter
#Builder
public static class ErrorResponse {
private String status;
private String reason;
}
}
By using MissingRequestHeaderException, it will throw an exception if what you've annotated with #RequestHeader is missing, so you will get an exception like this:
Missing request header 'Etag' for method parameter of type int
And when the request header is present but not valid this exception will be thrown:
Invalid request header provided.

Spring REST Multipart error handling

I have this REST Controller that should also handle exceptions.
The #ExceptionHandler(MultipartException.class) annotation is not working as explained.
So I am implementing HandlerExceptionResolver which is basically working but is not as handy for REST and JSON responses as #ExceptionHandler would be.
I would like to return my custom class ValidationReport in resolveException similar to the #ExceptionHandler handleBadRequest. I was not able to create a ModelAndView with a ValidationReport json response. Any Idea how I can combine both styles?
#RestController
class ValidationController implements HandlerExceptionResolver{
static Logger LOG = LoggerFactory.getLogger(ValidationController.class);
#RequestMapping(value="/validate", method=[POST])
public ValidationReport validate(MultipartFile file) {
LOG.info("received file ${file?.name}")
ValidationReport report = new ValidationReport();
return report
}
#ResponseStatus(BAD_REQUEST)
#ExceptionHandler(MultipartException.class)
#ResponseBody ValidationReport handleBadRequest(HttpServletRequest req, Exception ex) {
return new ValidationReport(USER_ERROR, "you should not upload files bigger then xx MB")
}
#Override
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex instanceof MultipartException){
response.sendError(BAD_REQUEST.value(),ex.message)
}
return null
}
}
I
This is not a solution I am not really happy with but one that works. I implement the HandlerExceptionResolver Interface to catch all exceptions.
In the implemented Method I handle only the exception I am interested in. Then I send the caller a error code and tell him what he did wrong.
#Override
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex instanceof MultipartException){
response.sendError(413,"Content is to big. Maximal allowed request size is: ${Application.MAX_REQUEST_SIZE}")
}
}

Spring controller throwing HttpStatus.UNAUTHORIZED fires 500 Http error instead of 401

Here's the scenario :
I created the following custom response exception, to fire the 401 Http Status :
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class HttpUnauthorizedException extends RuntimeException {
}
The controller that uses the exception :
#Controller
public UserController {
#RequestMapping(value = "api/user")
#ResponseBody
public String doLogin(
#RequestParam(value = "username", required = false) String username, #RequestParam(value = "password", required = false) String password) {
if(userLoggedIn(String username, String password)) {
return "OK";
}
else {
throw new HttpUnauthorizedException();
}
}
...
}
Now when I try to access the controller to see the 401 exception, the server fires the Http error code 500 instead. But interestingly enough, when I try with the HttpStatus.NOT_FOUND it actually works, the server fires 404. Is there something I'm missing on here?
Thanks in advance :-)
First throw new HttpUnauthorizedException();
then you can catch it at a normal controller that have #ControllerAdvice annotation
#ControllerAdvice // To Handle Exceptions
public class ExceptionController {
//// ...........
#ExceptionHandler({HttpUnauthorizedException.class})
#ResponseBody
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
Map<String, String> unauthorizedAccess(Exception e) {
Map<String, String> exception = new HashMap<String, String>();
log.error("unauthorized Access to the API: " + e.getMessage(), e);
exception.put("code", "401");
exception.put("reason", e.getMessage());
return exception;
}
}
I think code should be much simpler, maybe the answer was written with old Spring version.
In this example I've implemented method to handle exception - HttpClientErrorException.Unauthorized to cover authentication issue (401):
#ControllerAdvice
public class MyErrorsHandler extends ResponseEntityExceptionHandler
{
#ExceptionHandler(HttpClientErrorException.Unauthorized.class)
protected ResponseEntity<Object> handleAuthenticationError(RuntimeException ex, WebRequest request)
{
return handleExceptionInternal(ex,
"Cannot login, please check your inputs",
new HttpHeaders(), HttpStatus.UNAUTHORIZED, request);
}
}
Finally I get correct error to GUI

Request method 'POST' not supported when throwing exception

I am throwing exception in a scenario. Which is handled by #ExceptionHandler. But when throwing exception it says Request method 'POST' not supported
Controller code
#RequestMapping(value = "abcd", method = {RequestMethod.POST,RequestMethod.GET })
public String testAbc(Model model, HttpServletRequest request) throws Exception {
//some piece of code
if(someCondition)
throw new Exception("No data found with id ");
}
Code in ExceptionController class
#ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("errorMessage", ex.getMessage());
modelAndView.addObject("errorDetails", ExceptionUtils.getStackTrace(ex));
modelAndView.setViewName("forward:errorPage");
return modelAndView;
}
Have no idea what I am doing wrong.
It seems that the controller that handles /errorPage does not take request method POST. In your #ExceptionHandler method, you are doing a forward to that page by setting view name to forward:errorPage.
Can you confirm if errorPage controller does handle POST method.

Categories