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
Related
I am developing a Spring Boot based REST API. I am validating the input entities using custom ConstraintValidator annotations. My problem is that I cannot return the ConstraintViolationException messages in the response. My exception handler does not catch the exceptions (maybe because they're wrapped in another types of exceptions).
Can I please get some advice on how to handle the situation?
I've searched all over the Internet but I couldn't find a fitting solution for me and I've also wasted some hours doing so.
Example annotation:
#Documented
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#Constraint(validatedBy = BirthDateValidator.class)
public #interface ValidBirthDate {
String message() default "The birth date is not valid.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator class:
public class BirthDateValidator extends FieldValidationBase implements ConstraintValidator<ValidBirthDate, LocalDate> {
private static final Logger LOGGER = LoggerFactory.getLogger(BirthDateValidator.class);
#Override
public void initialize(ValidBirthDate constraintAnnotation) {
}
#Override
public boolean isValid(LocalDate birthDate, ConstraintValidatorContext constraintValidatorContext) {
constraintValidatorContext.disableDefaultConstraintViolation();
LOGGER.info("Starting the validation process for birth date {}.", birthDate);
if(birthDate == null) {
constraintValidatorContext.buildConstraintViolationWithTemplate("The birth date is null.")
.addConstraintViolation();
return false;
}
//other validations
return true;
}
Model class:
public class Manager extends BaseUser {
//other fields
#Valid
#ValidBirthDate
private LocalDate birthDate;
//setters & getters
Exception handler:
#ExceptionHandler(value = ConstraintViolationException.class)
public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
List<String> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
errors.add(violation.getRootBeanClass().getName() + ": " + violation.getMessage());
}
Error response = new Error(errors);
return new ResponseEntity<Object>(response, new HttpHeaders(), BAD_REQUEST);
}
The controller:
#RestController
#RequestMapping(value = "/register", consumes = "application/json", produces = "application/json")
public class RegistrationController {
#Autowired
private RegistrationService registrationService;
#PostMapping(value = "/manager")
public ResponseEntity registerManager(#RequestBody #Valid Manager manager) {
registrationService.executeSelfUserRegistration(manager);
return new ResponseEntity<>(new Message("User " + manager.getEmailAddress() + " registered successfully!"), CREATED);
}
}
I get the 400 response code, but I am not seeing any response body containing the violated constraint messages.
After some more debugging, I found out that all constraint violations were wrapped into a MethodArgumentNotValidException (because of the #Valid annotations) - I had to dig a bit inside that exception to get my information.
I've overriden the handleMethodArgumentNotValid() method from ResponseEntityExceptionHandler and this is how I got it to work:
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> errorMessages = new ArrayList<>();
BindingResult bindingResult = ex.getBindingResult();
List<ObjectError> errors = bindingResult.getAllErrors();
for(ObjectError error : errors) {
String message = error.getDefaultMessage();
errorMessages.add(message);
}
return new ResponseEntity<>(new Error(errorMessages), new HttpHeaders(), BAD_REQUEST);
}
Maybe this helps someone.
When the target argument fails to pass the validation, Spring Boot throws a MethodArgumentNotValidException exception. I have extracted the error message from bindingResult of this exception as shown below:
#RestControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
//to extract the default error message from a diagnostic
// information about the errors held in MethodArgumentNotValidException
Exception exception = new Exception(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return this.createResponseEntity(HttpStatus.BAD_REQUEST, exception, request);
}
private ResponseEntity<Object> createResponseEntity(
HttpStatus httpStatus, Exception ex, WebRequest request) {
ErrorResponse errorResponse = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(httpStatus.value())
.error(httpStatus.getReasonPhrase())
.message(ex.getMessage())
.path(request.getDescription(true))
.build();
return handleExceptionInternal(ex, errorResponse,
new HttpHeaders(), httpStatus, request);
}
}
ErrorResponse class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
}
The response will be 400 with body in JSON format as shown below:
{
"timestamp": "2021-01-20T10:30:15.011468",
"status": 400,
"error": "Bad Request",
"message": "Due date should not be greater than or equal to Repeat Until Date.",
"path": "uri=/api/tasks;client=172.18.0.5;user=109634489423695603526"
}
I hope this helps. If you need a detailed explanation on class-level constraint, have a look at this video.
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);
}
I am testing an #RestController which has an API endpoint such as /api/dataobject. If the object (in JSON format) that is posted to this endpoint is missing some part of its meta data, the API should respond with a Http status of bad request (400).
When testing it through Postman, this works, however in my unit test where the controller is mocked it still returns a status 200.
The method in the RestController:
#RequestMapping("/api/dataobject")
public ResponseEntity postDataObject(#RequestBody final DataObject dataObject) throws InvalidObjectException {
if (!dataObjectValidator.validateDataObject(dataObject)) {
throw new InvalidObjectException("Data object was invalid: " + dataObject.toString());
}
return new ResponseEntity(HttpStatus.OK);
}
The InvalidObjectException is caught by a class annotated with #ControllerAdvice which extends ResponseEntityExceptionHandler and is handled as follows:
#ExceptionHandler(value = InvalidObjectException.class)
protected ResponseEntity<Object> handleInvalidObject(final InvalidObjectException exception, final WebRequest request) {
final String bodyOfResponse = exception.getMessage();
return handleExceptionInternal(exception, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
Now, the unit test class is as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(DataObjectController.class)
public class DataObjectControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private DataObjectController dataObjectController;
private final String uri = "/api/idataobject";
#Test
public void noAppName() throws Exception {
DataObject object = getDataObjectNoAppName();
final String body = new Gson().toJson(object);
given(dataObjectController.postDataObject(object)).willReturn(new ResponseEntity(HttpStatus.BAD_REQUEST));
mvc.perform(post(uri)
.content(body)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
}
Even though the object is invalid, and I've said that the given object would return a HttpStatus 400, I get a 200 status in return.
Clearly I'm missing something here, but what?
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 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.