customizing error response message when #requestparam is missing - java

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.

Related

Is it possible to create multiple custom validation message for #Valid argument exception?

I want to create custom message for my custom validation annotation. If this validation failed, it throws MethodArgumentNotValidException. Because I put #Valid for the #RequestBody.
I create #ControllerAdvice to handle/ override MethodArgumentNotValidException message. I have this enum error class separately. So every error that throws this kind of exception will throws the same message as Invalid Param
My question is is it possible to exclude my custom validation message and throws different message instead? How to make this #MyCustomAnnotation throws different exception message? Not fall under MethodArgumentNotValidException message.
My custom validation interface
#Documented
#Constraint(validatedBy = myValidator.class)
#Target({FIELD, METHOD, ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface CheckMyCode{
String message() default "{my custom string message}";
Class<?> [] groups() default {};
Class<? extends Payload>[] payload() default {};
}
My controller advice method to handle all MethodArgumentNotValidException exception
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers, HttpStatus status, WebRequest request){
CustomClassError<Object> error = CustomClassError.failure(ErrCode.INVALID_PARAM);
return new ResponseEntity<Object>(error, new HttpHeaders(), HttpStatus.OK);
}
You could override the handleMethodArgumentNotValid method in controller advice like (seems this is the use case you need ):
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> details = new ArrayList<>();
for(ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
ErrorResponse error = new ErrorResponse("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
Or otherwise in your controller class you could handle it by throwing a custom exception and then handling it separately in your controller advice (not for validation exception but for all other run time exceptions this approach is preferable for handling the exception):
#PostMapping("/addValidate")
public String submitForm(#Valid ValidatedData validatedData,
BindingResult result, Model m) {
if(result.hasErrors()) {
List<FieldError> errors = bindingResult.getFieldErrors(); // provided only for your information
for (FieldError error : errors ) {
System.out.println (error.getObjectName() + " - " +error.getDefaultMessage());
}
throw new CustomExceptionClass("Your could assign the field error custom message here");
}
m.addAttribute("message", "Success"
+ validatedData.toString());
return "Home";
}
and then in your controller advice class you could use this message passed to the exception like :
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage()); //just call the message method to get the message in the exception
ErrorResponse error = new ErrorResponse("Server Error", details); //Custom error response class to return json response
return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
}

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

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 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