Return response code as 400 when #notnull validation fails - java

I'm using javax and Hibernate implementation for validation of my request payload.
version - org.hibernate:hibernate-validator:5.4.1.final
Sample Pojo:
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
public class Customer implements Serializable{
#NotNull(message="name is null")
#Size(min = 1)
public String name;
#NotNull(message="country is null")
#Size(min = 1)
public String country;
}
Sample controller:
#RequestMapping(value = {"/customer"}, method = RequestMethod.POST)
#ResponseBody
public Customer doAdd(#RequestBody #Valid Customer inData){
//some logic
return outData
}
Sample input json:
{
"country":"canada"
}
Console Exception:
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument at index 0 in method: ........ model.request.Customer,java.lang.String,java.lang.String,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse), with 1 error(s): [Field error in object 'Customer' on field 'name': rejected value [null]; codes [NotNull.Customer.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [Customer.name,name]; arguments []; default message [name]]; default message [name is null]]
Here when customer.name is null, i'm getting response code as 422(Unprocessable entity). But i want to return 400(BAD Request). How can i override the response code here? Any document reference would be appreciated.
note - i don't want to do these validation at controller's side, where can i can check and send the response code accordingly.
Like this one - How to return 400 http status code with #NotNull?
Update - issue was resolved -------
The issue is resolved for me. Thanks all for your response.
Let me explain what was causing this,
HttpRequest ----> #Valid on RequestBody ----> Javax validation on Request object-----> If any of the validation fails, **MethodArgumentNotValidException** exception is thrown.
It is the responsibility of the developer to catch this exception and throw corresponding http response code.
In my case, the exception handler was already there and it was catching this MethodArgumentNotValidException and returning HttpStatus.UNPROCESSABLE_ENTITY. Thats the reason i was seeing 422 error code.
Now i have changed the exception handler as below,
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResource>
handleMethodArgumentNotValidException(MethodArgumentNotValidException exception, HttpServletRequest request) {
List<FieldErrorResource> fieldErrorResources = new ArrayList<>();
BindingResult bindingResult = exception.getBindingResult();
for (FieldError constraintViolation : bindingResult.getFieldErrors()) {
fieldErrorResources.add(FieldErrorResource.builder()
.field(constraintViolation.getField())
.resource(request.getContextPath())
.message(constraintViolation.getDefaultMessage()).build());
}
return responseEntityFor(HttpStatus.BAD_REQUEST,
"The content you've sent contains " + bindingResult.getErrorCount() + " validation errors.", fieldErrorResources);
}

You should set BindingResult immediately after your Customer. Like:
#RequestMapping(value = {"/customer"}, method = RequestMethod.POST)
#ResponseBody
public Customer doAdd(#RequestBody #Valid Customer inData, BindingResult bindingResult){
//some logic
return outData
}

You can use RestControllerAdvice for the same
#RestControllerAdvice
public class ExceptionRestControllerAdvice {
#ExceptionHandler({ConstraintViolationException.class})
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ExceptionResponseMessage handleInvalidParameterException(RuntimeException ex) {
return sendResponse(HttpStatus.BAD_REQUEST, ex);
}
private ExceptionResponseMessage sendResponse(HttpStatus status, RuntimeException ex) {
return new ExceptionResponseMessage(Instant.now(), status.value(), status.getReasonPhrase(),
ex.getClass().toString(), ex.getMessage());
}
}
public class ExceptionResponseMessage {
private Instant time;
private int status;
private String error;
private String exception;
private String message;
// setter and getter and constructor

Related

How to intercept the error message when spring boot web binding?

I try to make user registration codes with spring boot web starter.
First, these are registration form class including constraints.
#Data
public class RegisterForm {
#NotBlank(message = "Not valid username.") // (1)
#Size(min=2, max=30, message="minimum 2 and maximum 30") // (3)
private String username;
#NotBlank(message = "Not valid password") // (1)
#Size(min=5, message = "minimum 5") // (3)
private String password;
#Size(max=50, message="maximum 50") // (3)
private String fullname;
#NotEmpty(message = "Not valid email") // (2)
private String email;
}
And next codes are controller classes which bind User class and registration form class.
#RequestMapping(value="/users/register", method=RequestMethod.POST)
public String register(#Valid RegisterForm registerForm, Model model, BindingResult bindingResult) {
if(!bindingResult.hasErrors()) {
User user = new User();
user.setUsername(registerForm.getUsername());
user.setPassword(registerForm.getPassword());
user.setEmail(registerForm.getEmail());
user.setFullname(registerForm.getFullname());
user.setRole(UserRole.USER);
this.userService.register(user);
return "redirect:/home";
}
return "/users/register";
}
And Below codes are Error Controller class.
#RequestMapping("/error")
public String errorHandle(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if(status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if(statusCode.equals(HttpStatus.BAD_REQUEST.value())) {
return "/errors/400";
} else if(statusCode.equals(HttpStatus.NOT_FOUND.value())) {
return "/errors/404";
} else if(statusCode.equals(HttpStatus.FORBIDDEN.value())) {
return "/errors/403";
} else if(statusCode.equals(HttpStatus.INTERNAL_SERVER_ERROR.value())) {
return "/errors/500";
}
}
return "errors/default";
}
And I make the error intentionally ,and then the error message are brought on the console like below and 400 exception is thrown with /error/400 html.
Field error in object 'registerForm' on field 'username': rejected value []; default message [minimum 2 and maximum 30]
Field error in object 'registerForm' on field 'username': rejected value []; default message [Not valid username]
Field error in object 'registerForm' on field 'email': rejected value []; default message [Not valid email]
Field error in object 'registerForm' on field 'password': rejected value []; default message [Not valid password]
Field error in object 'registerForm' on field 'password': rejected value [];default message [minimum 5]]
My issue is I have no idea how to send the field error of registerForm messages to /error/400 html so the user can confirm which field of registerForm violates the constraint. I want to know how field error of registerForm can be transferred to /error/400 html. Any idea, please.
first step:
validate data in the controller like this
#RequestMapping(value="/users/register", method=RequestMethod.POST)
public String register(#Valid RegisterForm registerForm)......
sencond step:
make a controller advice which catch the exceptions
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(value = MethodArgumentNotValidException.class)
#ResponseBody
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
//here you can use api of MethodArgumentNotValidException to do anything you want
//e.getBindingResult(),e.getFieldErrors(),etc;
// you can change the return type of Object
}
You need to extends ResponseEntityExceptionHandler which provide centralized exception handling across all RequestMapping methods. This base class provides an ExceptionHandler method for handling internal Spring MVC exceptions. This method returns a ResponseEntity for writing to the response with a HttpMessageConverter message converter.
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
// All Exceptions Handler.
#ExceptionHandler(Exception.class)
public final ResponseEntity<ExceptionBean> handleAllExceptions(Exception ex, WebRequest request) {...}
// Unique Constraint Violation Exception Handler.
#ExceptionHandler(DataIntegrityViolationException.class)
public final ResponseEntity<ExceptionBean> handleDataIntegrityViolationExceptions(DataIntegrityViolationException ex, WebRequest request) {...}
// Custom Exceptions Handler.
#ExceptionHandler(DomainException.class)
public final ResponseEntity<ExceptionBean> handleDomainExceptions(DomainException ex, WebRequest request) {
ExceptionBean exceptionBean = new ExceptionBean(new Date(), ex.getMessage(),
request.getDescription(false));
LOGGER.error(ex.getMessage(), ex);
return new ResponseEntity<>(exceptionBean, HttpStatus.BAD_REQUEST);
}
}

Preventing Boolean from Junk Value in Object in Rest API Call

I have one Object which has 3 attributes having Integer,String and Boolean Data type in Java,looks like below given REST Body.
{
"famousForId": 3,
"name": "Food",
"activeState": true
}
i've one pojo class which looks like this
#Data
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Document(collection="famousFor")
public class FamousFor {
// #Id
#NotNull
#PositiveOrZero
Long famousForId;
#NotBlank(message = ApplicationUtil.MISSING_FIELD)
#Pattern(regexp = "^[A-Za-z0-9]+$")
#Pattern(regexp = "^[A-Za-z0-9]+$",message = "Name Can't Have Special Characters")
String name;
#NotNull(message = ApplicationUtil.MISSING_FIELD)
Boolean activeState;
}
and my controller is handling errors properly any junk value for Long and String Value, however if we are passing junk value for Boolean it's without showing any error, it directly throws Http code 400 bad request, i want the proper message for this attribute as well,any help would be highly appreciated.
I'm using Spring boot 2.0.6
My Controller looks something like the below given code block
#RequestMapping(value="/saveFamousForDetails",produces={"application/json"},method=RequestMethod.POST)
ResponseEntity<?> saveEssentialDetails(#ApiParam(value="Body Parameters") #RequestBody #Validated #ModelAttribute("famousFor") FamousFor famousFor, BindingResult bindingResult)throws Exception;
One way I can think of is writing a ExceptionHandler class to your controller using #ControllerAdvice. Override "handleHttpMessageNotReadable" method.
#ControllerAdvice
#Log4j2
public class ExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException exception,
HttpHeaders headers, HttpStatus status, WebRequest request) {
// log the error and return whatever you want to
}
}

#NotNull : validation custom message not displaying

I am using spring data JPA for creating application. In that I am trying to implement server side validation using annotation. I added #NotNull annotation on filed with custom message. I also added #valid with #RequestBody
But problem is that when I am passing nAccountId as null I am not getting custom message i.e. Account id can not be null I am getting "message": "Validation failed for object='accountMaintenanceSave'. Error count: 1",.
Can any one please tell me why I am not getting custom message?
Controller code
#PutMapping("/updateAccountData")
public ResponseEntity<Object> saveData(#Valid #RequestBody AccountMaintenanceSave saveObj){
return accService.saveData(saveObj);
}
AccountMaintenanceSave class
import javax.validation.constraints.NotNull;
public class AccountMaintenanceSave {
#NotNull(message="Account id can not be null")
public Integer nAccountId;
#NotNull
public String sClientAcctId;
#NotNull
public String sAcctDesc;
#NotNull
public String sLocation;
#NotNull
public Integer nDeptId;
#NotNull
public Integer nAccountCPCMappingid;
#NotNull
public Integer nInvestigatorId;
//Getter and Setter
}
RestExceptionHandler class
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptionMethod(Exception ex, WebRequest requset) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
exceptionMessageObj.setMessage(ex.getLocalizedMessage());
exceptionMessageObj.setError(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
// return exceptionMessageObj;
return new ResponseEntity<>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I don't know what exactly happen previously and not getting proper message. Now using same code getting result like this with proper message
{
"message": "Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<java.lang.Object> com.spacestudy.controller.AccountController.saveData(com.spacestudy.model.AccountMaintenanceSave), with 1 error(s): [Field error in object 'accountMaintenanceSave' on field 'nAccountId': rejected value [null]; codes [NotNull.accountMaintenanceSave.nAccountId,NotNull.nAccountId,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [accountMaintenanceSave.nAccountId,nAccountId]; arguments []; default message [nAccountId]]; default message [Account id can not be null]] ",
"error": "org.springframework.web.bind.MethodArgumentNotValidException",
"path": "/spacestudy/rockefeller/admin/account/updateAccountData"
}
In message filed can I print only [Account id can not be null]?
Try this.
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptionMethod(Exception ex, WebRequest requset) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
// Handle All Field Validation Errors
if(ex instanceof MethodArgumentNotValidException) {
StringBuilder sb = new StringBuilder();
List<FieldError> fieldErrors = ((MethodArgumentNotValidException) ex).getBindingResult().getFieldErrors();
for(FieldError fieldError: fieldErrors){
sb.append(fieldError.getDefaultMessage());
sb.append(";");
}
exceptionMessageObj.setMessage(sb.toString());
}else{
exceptionMessageObj.setMessage(ex.getLocalizedMessage());
}
exceptionMessageObj.setError(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
// return exceptionMessageObj;
return new ResponseEntity<>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
It's not so good to make your only ExceptionHandler to catch Exception.class make it ConstraintViolationException.class
Another approach to the solution:
I would suggest remove the exception handler for this validation fields in the POJO and rather let Spring handle the error response by adding the below property into application.properties. By adding this the message configured in the #notnull or any validation annotation can be captured and showed in the response by default and no explicit handling of this validation case is required.
server.error.include-message=always
server.error.include-binding-errors=always

Spring Boot Validate JSON Mapped via ObjectMapper GET #RequestParam

What's the simplest approach to validating a complex JSON object being passed into a GET REST contoller in spring boot that I am mapping with com.fasterxml.jackson.databind.ObjectMapper?
Here is the controller:
#RestController
#RequestMapping("/products")
public class ProductsController {
#GetMapping
public ProductResponse getProducts(
#RequestParam(value = "params") String requestItem
) throws IOException {
final ProductRequest productRequest =
new ObjectMapper()
.readValue(requestItem, ProductRequest.class);
return productRetriever.getProductEarliestAvailabilities(productRequest);
}}
DTO request object I want to validate:
public class ProductRequest {
private String productId;
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}}
I was thinking of using annotations on the request DTO however when I do so, they are not triggering any type of exceptions, i.e. #NotNull. I've tried various combinations of using #Validated at the controller as well as #Valid in the #RequestParam and nothing is causing the validations to trigger.
In my point of view, Hibernate Bean Validator is probably one of the most convenient methods to validate the annotated fields of a bean anytime and anywhere. It's like setup and forget
Setup the Hibernate Bean Validator
Configure how the validation should be done
Trigger the validator on a bean anywhere
I followed the instructions in the documentation given here
Setup dependencies
I use Gradle so, I am going to add the required dependencies as shown below
// Hibernate Bean validator
compile('org.hibernate:hibernate-validator:5.2.4.Final')
Create a generic bean valdiator
I setup a bean validator interface as described in the documentation and then use this to validate everything that is annotated
public interface CustomBeanValidator {
/**
* Validate all annotated fields of a DTO object and collect all the validation and then throw them all at once.
*
* #param object
*/
public <T> void validateFields(T object);
}
Implement the above interface as follow
#Component
public class CustomBeanValidatorImpl implements CustomBeanValidator {
ValidatorFactory valdiatorFactory = null;
public CustomBeanValidatorImpl() {
valdiatorFactory = Validation.buildDefaultValidatorFactory();
}
#Override
public <T> void validateFields(T object) throws ValidationsFatalException {
Validator validator = valdiatorFactory.getValidator();
Set<ConstraintViolation<T>> failedValidations = validator.validate(object);
if (!failedValidations.isEmpty()) {
List<String> allErrors = failedValidations.stream().map(failure -> failure.getMessage())
.collect(Collectors.toList());
throw new ValidationsFatalException("Validation failure; Invalid request.", allErrors);
}
}
}
The Exception class
The ValidationsFatalException I used above is a custom exception class that extends RuntimeException. As you can see I am passing a message and a list of violations in case the DTO has more than one validation error.
public class ValidationsFatalException extends RuntimeException {
private String message;
private Throwable cause;
private List<String> details;
public ValidationsFatalException(String message, Throwable cause) {
super(message, cause);
}
public ValidationsFatalException(String message, Throwable cause, List<String> details) {
super(message, cause);
this.details = details;
}
public List<String> getDetails() {
return details;
}
}
Simulation of your scenario
In order to test whether this is working or not, I literally used your code to test and here is what I did
Create an endpoint as shown above
Autowire the CustomBeanValidator and trigger it's validateFields method passing the productRequest into it as shown below
Create a ProductRequest class as shown above
I annotated the productId with #NotNull and #Length(min=5, max=10)
I used Postman to make a GET request with a params having a value that is url-encoded json body
Assuming that the CustomBeanValidator is autowired in the controller, trigger the validation as follow after constructing the productRequest object.
beanValidator.validateFields(productRequest);
The above will throw exception if any violations based on annotations used.
How is the exception handled by exception controller?
As mentioned in the title, I use ExceptionController in order to handle the exceptions in my application.
Here is how the skeleton of my exception handler where the ValidationsFatalException maps to and then I update the message and set my desired status code based on exception type and return a custom object (i.e. the json you see below)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({SomeOtherException.class, ValidationsFatalException.class})
public #ResponseBody Object handleBadRequestExpection(HttpServletRequest req, Exception ex) {
if(ex instanceof CustomBadRequestException)
return new CustomResponse(400, HttpStatus.BAD_REQUEST, ex.getMessage());
else
return new DetailedCustomResponse(400, HttpStatus.BAD_REQUEST, ex.getMessage(),((ValidationsFatalException) ex).getDetails());
}
Test 1
Raw params = {"productId":"abc123"}
Url encoded parmas = %7B%22productId%22%3A%22abc123%22%7D
Final URL: http://localhost:8080/app/product?params=%7B%22productId%22%3A%22abc123%22%7D
Result: All good.
Test 2
Raw params = {"productId":"ab"}
Url encoded parmas = %7B%22productId%22%3A%22ab%22%7D
Final URL: http://localhost:8080/app/product?params=%7B%22productId%22%3A%22ab%22%7D
Result:
{
"statusCode": 400,
"status": "BAD_REQUEST",
"message": "Validation failure; Invalid request.",
"details": [
"length must be between 5 and 10"
]
}
You can expand the Validator implementation to provide a mapping of field vs message error message.
Do you mean something like this ?
#RequestMapping("/products")
public ResponseEntity getProducts(
#RequestParam(value = "params") String requestItem) throws IOException {
ProductRequest request = new ObjectMapper().
readValue(requestItem, ProductRequest.class);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ProductRequest>> violations
= validator.validate(request);
if (!violations.isEmpty()) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
public class ProductRequest {
#NotNull
#Size(min = 3)
private String id;
public String getId() {
return id;
}
public String setId( String id) {
return this.id = id;
}
}

How to know if a parameter value is provided in post request body in spring?

I am building web service with spring and come across with following problem.
There is a post service as follow.
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto){
//post data
//return response
}
public class ADto{
private String firstParam;
private String secondParam;
// getter setter
}
So, my question is how can I know whether value of firstParam and secondParam is provided in request body or not.
RequestBody: { paramFirst: null, paramSecond: null}
Edit1:
Sorry for incomplete question:
For RequestBody: {paramFirst: first Value} and for above request value of paramSecond will be null.
So, how would I know whether paramSecond is included in request or not.
Edit2:
I don't want to validate. What I want to know is whether
request contains a particular parameter or not.
Because there are two different cases, one is value of a parameter is given null and other is paramter is not included in request.
You could use the #Valid annotation like so (pseudo code, didn't test it):
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#Valid #RequestBody ADto aDto){
// MethodArgumentNotValidException will be thrown if validation fails.
}
You'll need an exception handler to handle the validation error.
#ExceptionHandler
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public Error handleException(MethodArgumentNotValidException exception) {
//do something with the validation message: exception.getBindingResult()
}
And your class.
public class ADto{
#NotNull(message = "First parameter can not be null")
private String firstParam;
#NotNull(message = "Second parameter can not be null")
private String secondParam;
// getter setter
}
Try using Hibernate Validator (http://hibernate.org/validator/), it's really easy to integrate it with Spring.
That way, you'll need to annotate your Dto to enforce validation of required params and then call validate.
public class ADto{
#NotNull
private String firstParam;
#NotNull
private String secondParam;
// getter setter
}
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto){
validator.validate(aDto)
//post data
//return response
}
You could make firstParam and secondParam type Optional:
ADto class
public class ADto {
private Optional<String> firstParam;
private Optional<String> secondParam;
// getter setter
}
postSomething method
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto) {
if (Optional.ofNullable(aDto.getFirstParam()).isPresent()) {
// firstParam is provided in the request
} else {
// firstParam is not provided in the request
}
if (Optional.ofNullable(aDto.getSecondParam()).isPresent()) {
// secondtParam is provided in the request
} else {
// secondtParam is not provided in the request
}
}
Note that isPresent() will return false if and only if firstParam (as well as for secondParam) is not present in the request. Otherwise, even if the value is set to null, it will return true.

Categories

Resources