Intercept #RequestHeader exception for missing header - java

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.

Related

Why ExceptionHandlerExceptionResolver makes redirect instead of simple response?

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

Return HTTP code 200 from Spring REST API

I want to use this code to receive http link with values:
#PostMapping(value = "/v1/notification")
public String handleNotifications(#RequestParam("notification") String itemid) {
// parse here the values
return "result successful result";
}
How I can return http code 200 - successful response?
And also for example if there is a code exception into code processing how can I return error 404?
If you are using spring:
#PostMapping(value = "/v1/notification")
public ResponseEntity handleNotifications(#RequestParam("notification") String itemid) {
// parse here the values
return ResponseEntity.ok().build();
//OR ResponseEntity.ok("body goes here");
}
If you use #RestController it should return 200 by default.
But anyway, you can set a particular response status by #ResponseStatus annotation (even if the methods returns void) or you can return a custom response by ResponseEntity.
EDIT: added error handling
For error handling, you can return a particular response entity:
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body("some body ");
or you can use #ExceptionHandler:
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public void handleError(Exception ex) {
// TODO: log exception
}
You can do it by annotating your method with #ResponseStatus using HttpStatus.OK (However it should be 200 by default), like this:
Some controller
#PostMapping(value = "/v1/notification")
#ResponseStatus(HttpStatus.OK)
public String handleNotifications(#RequestParam("notification") String itemid) throws MyException {
if(someCondition) {
throw new MyException("some message");
}
// parse here the values
return "result successful result";
}
Now, in order to return a custom code when handling a specific exception you can create a whole separate controller for doing this (you can do it in the same controller, though) which extends from ResponseEntityExceptionHandler and is annotated with #RestControllerAdvice and it must have a method for handling that specific exception as shown below:
Exception handling controller
#RestControllerAdvice
public class ExceptionHandlerController extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
protected ResponseEntity<Object> handleMyException(MyException ex, WebRequest req) {
Object resBody = "some message";
return handleExceptionInternal(ex, resBody, new HttpHeaders(), HttpStatus.NOT_FOUND, req);
}
}
You can do something like this:
#PostMapping(value = "/v1/notification")
public ResponseEntity<String> handleNotifications(
#RequestParam("notification") String itemid) {
// parse here the values
return new ResponseEntity<>("result successful result",
HttpStatus.OK);
}

Validate request parameter Date in spring rest controller

I want to validate date as request parameter.
My endpoint url is like
http://localhost:8080/api/get/getCurrencyRate?date=02-20-2017
Controller:
#RequestMapping(value = "/getCurrencyRate", produces={"application/json"},
method = RequestMethod.GET)
public CurrenctRate getCurrencyrate(#RequestHeader ("Authorization") String
authorization, #RequestParam(value="date") #DateTimeFormat(pattern="MM-dd-
yyyy") #Valid Date date) throws Exception {
For the above input (02-20-2017) service is working fine. I want to validate the request param send appropiate response to the user. How can I do that.
e.g.
if the request is like
http://localhost:8080/api/get/getCurrencyRate?date=02/20/2017
response should be "Please enter date in "MM-DD-YYYY" format"
whereas now I am getting
Error code **400**
<b>JBWEB000069: description</b>
<u>JBWEB000120:
- The request sent by the client was syntactically incorrect.
</u>
Please advice.
The best solution I can think of is to have methods for all types of date format BUT formation the path, or use path parameters, like so:
//Using Path
#RequestMapping(value = "/getCurrencyRate/{date}", produces={"application/json"}, method = RequestMethod.GET)
public CurrenctRate getCurrencyRateOfDate(#RequestHeader ("Authorization") String authorization, #PathVariable("date") #DateTimeFormat(pattern="MM/dd/yyyy") #Valid Date date) throws Exception {
OR, with request parameter
//Using Request Parameter
#RequestMapping(value = "/getCurrencyRate", produces={"application/json"}, method = RequestMethod.GET)
public CurrenctRate getCurrencyrate(#RequestHeader ("Authorization") String authorization, #RequestParam(value="date") #DateTimeFormat(pattern="MM/dd/yyyy") #Valid Date date) throws Exception {
That way, Spring REST can match your request to your API call.
You have to use #ControllerAdvice, create exception handler for MethodArgumentTypeMismatchException exception type and also create class for your proper exception class which you need to send as a response to the client. For instance,
I have #ControllerAdvice class RestErrorHandler with below exceptionhandler for HttpMessageNotReadableException exception.
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ValidationErrorDTO> processValidationIllegalError(HttpMessageNotReadableException ex,
HandlerMethod handlerMethod, WebRequest webRequest) {
Throwable throwable = ex.getMostSpecificCause();
ValidationErrorDTO errorDTO = new ValidationErrorDTO();
if (throwable instanceof EnumValidationException) {
EnumValidationException exception = (EnumValidationException) ex.getMostSpecificCause();
errorDTO.setEnumName(exception.getEnumName());
errorDTO.setEnumValue(exception.getEnumValue());
errorDTO.setErrorMessage(exception.getEnumValue() + " is an invalid " + exception.getEnumName());
}
return new ResponseEntity<ValidationErrorDTO>(errorDTO, HttpStatus.BAD_REQUEST);
}
ValidationErrorDTO is the class having few setter/getters and when HttpMessageNotReadableException exception occurs then send ValidationErrorDTO in the response with the message which I want the client to see.
I created custom exception handler extending ResponseEntityExceptionHandler with #ControllerAdvice. where I override
handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request). This way I created handled the exception and created my own response.
Please refer below:
#Override
protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
char quotes='"';
String error ="Invalid date "+ quotes+ ex.getValue()+quotes +".. Please enter date in MM/dd/YYYY.";
err (error);
CustomException customExcepton = new CustomException (HttpStatus.BAD_REQUEST, "101", ex.getLocalizedMessage(), error);
return new ResponseEntity <Object> (customExcepton, new HttpHeaders(), customExcepton.getStatus());
}
My CustomException class is:
#JsonInclude(Include.NON_NULL)
public class CustomException implements Serializable {
private static final long serialVersionUID = -6839345326601547899L;
private HttpStatus status;
private String exceptionCode;
private String exceptionMessage;
private List <String> errors = null;
public CustomException() {
// Default
}
public CustomException (HttpStatus status, String exceptionCode, String exceptionMessage, String error) {
super();
this.status = status;
this.exceptionCode = exceptionCode;
this.exceptionMessage = exceptionMessage;
this.errors = Arrays.asList (error);
}
//getters and setters

customizing error response message when #requestparam is missing

I have already looked at a bunch of existing pages on stackoverflow for this but none of these helped:
How to customize #RequestParam error 400 response in Spring MVC
How to inform callers when required rquest parameter is missing?
My problem is extremely similar:
My application is really a REST api, returning json/xml data rather that html (ie I don't use jsps, but marshalling to transform java beans to json/xml)
When I query the URL with a required parameter, I get a 400 status with nothing in the payload, no text, nada.
I would like to return a json/xml payload using my ErrorResponse object that I use in other places.
I am using Spring 3.2.5
I have a controller which maps a URL to a method with a required
parameter(candidateBillDay)
#Controller
public class AccountController extends WsController {
private static final String JSP_CANDIDATE_PDD = "candidatepdd";
#RequestMapping( value="/account/{externalId}/{externalIdType}/candidatepdd"
, method = RequestMethod.GET)
public String getCandidatePaymentDueDateInfo( ModelMap model
, #PathVariable String externalId
, #PathVariable Integer externalIdType
, #RequestParam Integer candidateBillDay
, #RequestParam(required=false) Boolean includeCurrent ){
...
model.addAttribute( CandidatePaymentDueDateResponse.ROOT_ELEMENT, ...));
return JSP_CANDIDATE_PDD;
}
}
I have an exception handler that catches all types of exceptions, has some logic to do some specific bits for some types (instanceof):
#ControllerAdvice
public class BWSExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(BWSExceptionHandler.class);
#ExceptionHandler(value = { Exception.class } )
public ResponseEntity<Object> handleOtherExceptions(final Exception ex, final WebRequest req) {
LOG.error("Uncaught Exception: ", ex);
ErrorResponse resp = null;
...
if( ex instanceof MissingServletRequestParameterException ){
MissingServletRequestParameterException e = (MissingServletRequestParameterException)ex;
resp = new ErrorResponse( Validatable.ERR_CODE_FIELD_NOT_POPULATED
, String.format( Validatable.MSG_FIELD_IS_REQUIRED
, e.getParameterName()
)
);
httpStatusCode = HttpStatus.BAD_REQUEST;
}
if(resp==null){
resp = new ErrorResponse(new ErrorElement("unknown_error", ex.getMessage()));
}
return handleExceptionInternal(ex, resp, new HttpHeaders(), httpStatusCode, req);
}
}
So this doesn't do anything when a parameter is missing. When I get an actual exception (ie account doesn't exist) then it does catch the exception and works as excepted. This leads me to think that no MissingServletRequestParameterException exception is thrown, which according to the doc, blogs and stackoverflow pages I've read should be thrown...
I have also tried implementing a class that extends DefaultHandlerExceptionResolver and override the handleMissingServletRequestParameter method with not much success ( following this blog: http://alexcuesta.wordpress.com/2011/05/11/error-handling-and-http-status-codes-with-spring-mvc/ )
Any idea of what I am doing wrong or what other option should I explore?
Try overriding handleMissingServletRequestParameter method in the BWSExceptionHandler class.
#ControllerAdvice
public class BWSExceptionHandler extends ResponseEntityExceptionHandler {
...
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
// MissingServletRequestParameterException handling code goes here.
}
...
#ExceptionHandler(value = { Exception.class } )
public ResponseEntity<Object> handleOtherExceptions(final Exception ex,
final WebRequest req) {
...
}
}
Hope this helps.

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

Categories