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.
Related
I've written a few extensions of ExceptionHandlerExceptionResolver, it intercepts all exceptions that it should, but instead of returning only error message and HTTP status code it makes really weird redirect by its own URL built upon users requested URL. For instance:
user's url -> .../myModule/api/myEntity/123 (it's an id)
resolver's redirect url -> .../myModule/api/myEntity/myEntity/123
Server doesn't have such resource and obviously it will respond with 404.
The question is: why it makes redirect and how to configure it to return only a message and status code?
My resolver:
public class BusinessLayerExceptionHandler extends ExceptionHandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView wrappedResponse = new ModelAndView();
wrappedResponse.addObject("errorMessage", ex.getMessage());
wrappedResponse.setStatus(HttpStatus.BAD_REQUEST);
return wrappedResponse;
}
}
I guess the usage of ModelAndView assumes redirection. At least that's a method description that I found in DispatcherServlet.
...
* #return a corresponding ModelAndView to forward to
* #throws Exception if no error ModelAndView found
*/
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
...
If so, how to make it return just error message and HTTP status code?
You can return just error message and HTTP status code by creating a custom View.
public class YourCustomView implements View {
private final String errorMessage;
public YourCustomView(String errorMessage) {
this.errorMessage = errorMessage;
}
#Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter pw = response.getWriter()) {
pw.write(errorMessage);
}
}
}
You need to put the custom View object into ModelAndView object in HandlerExceptionResolver#resolveException.
public class BusinessLayerExceptionHandler implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
ModelAndView wrappedResponse = new ModelAndView();
wrappedResponse.setStatus(HttpStatus.BAD_REQUEST);
wrappedResponse.setView(new YourCustomView(ex.getMessage()));
return wrappedResponse;
}
}
why it makes redirect
It seems that Spring recognize the view name as a defaultViewName and forwards to it (by calling RequestDispatcher#forward).
In DispatcherServlet#processHandlerException, a defaultViewName is set to the view name of a ModelAndView returned by resolveException when it doesn't have View object. A defaultViewName is got from DispatcherServlet#getDefaultViewName that translates a HTTP request into a view name.
Another Solution
I think you may be able to use #ControllerAdvice and #ExceptionHandler instead. It also can handle an exception thrown from a controller.
#ControllerAdvice
public class YourControllerAdvice {
#ExceptionHandler
public ResponseEntity<Map<String, String>> handleBusinessLayerException(
Exception exception) {
Map<String, String> body = Map.of("errorMessage", exception.getMessage());
return ResponseEntity.badRequest().body(body);
}
}
See Also
Spring Web MVC document about HandlerExceptionResolver
Spring Web MVC document about ControllerAdvice
I am using Spring Controller to handle some requests. I am getting above exception. I have handled application/json, application/xml, etc. But I am not sure about */* and how it should handle in controller. Here is my controller code.
#RequestMapping(value = "/handle", method = RequestMethod.POST)
public #ResponseBody Response handleRequest(HttpServletRequest request, #RequestBody TestDTO testDTO, HttpServletResponse httpServletResponse) throws Exception {
}
Here is the exception:
Unexpected error: Content type '*/*;charset=UTF-8' not supported
org.springframework.web.HttpMediaTypeNotSupportedException: Content type '*/*;charset=UTF-8' not supported
Please let me know I am missing something.
This is the final solution that worked for me. Removed #RequestBody and added consumes - MediaType.All_VALUE. Here is the code:
#RequestMapping(value = "/notify", method = RequestMethod.POST, consumes = {MediaType.ALL_VALUE})
public #ResponseBody Response notifyPaymentPost(HttpServletRequest request, PaymentDTO paymentDTO, HttpServletResponse httpServletResponse) throws Exception {
}
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.
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}")
}
}
How do I get the request/response that I can setcookie? Additionally, at the end of this method, how can I can redirect to another page?
#RequestMapping(value = "/dosomething", method = RequestMethod.GET)
public RETURNREDIRECTOBJ dosomething() throws IOException {
....
return returnredirectpagejsp;
}
How about this:
#RequestMapping(value = "/dosomething", method = RequestMethod.GET)
public ModelAndView dosomething(HttpServletRequest request, HttpServletResponse response) throws IOException {
// setup your Cookie here
response.setCookie(cookie)
ModelAndView mav = new ModelAndView();
mav.setViewName("redirect:/other-page");
return mav;
}
Just pass it as argument: public String doSomething(HttpServletRequest request). You can pass both the request and response, or each of them individually.
return the String "redirect:/viewname" (most often without the .jsp suffix)
For both questions, check the documentation, section "15.3.2.3 Supported handler method arguments and return types"
You can also simply #Autowire. For example:
#Autowired
private HttpServletRequest request;
Though HttpServletRequest is request-scoped bean, it does not require your controller to be request scoped, as for HttpServletRequest Spring will generate a proxy HttpServletRequest which is aware how to get the actual instance of request.
You could also use this way
#RequestMapping(value = "/url", method = RequestMethod.GET)
public String method(HttpServletRequest request, HttpServletResponse response){
Cookie newCookie = new Cookie("key", "value");
response.addCookie(newCookie);
return "redirect:/newurl";
}