I want to validate my request body with #Valid annotation, but it's not working in Spring Boot
I have a Request class within JAR file which I can't modify with two fields. One field is of type Object. My controller class accept this class object as a request body. When I pass my below JSON to the controller, validation is not working. Below are code samples.
Request Class:
public class Request {
Object data;
Map<String, Object> meta;
public <T> T getData() throws ClassCastException {
return (T) this.data;
}
}
Another Class:
public class StudentSignUpRequest {
#NotNull(message = "First Name should not be empty")
#Size(max = 64, message = "FirstName should not exceed 64 characters")
private String firstName;
#NotNull(message = "Last Name should not be empty")
#Size(max = 64, message = "LastName should not exceed 64 characters")
private String lastName;
#NotNull(message = "Email cannot be empty")
#Size(max = 50, message = "Email cannot exceed 50 characters")
#Pattern(regexp = EMAIL_REGEX_PATTERN, message = "Email should contain a valid email address.")
private String email;
// other fields
}
Controller Class:
#PostMapping(value = Constants.STUDENT_SIGN_UP)
public Response signUpStudent(#Valid #RequestBody Request request, HttpServletRequest servletRequest) {
// retrieving the actual resource from request payload
StudentSignUpRequest signUpRequest = request.getData(StudentSignUpRequest.class);
// call service to sign-up student
return loginRegistrationService.signUpStudent(signUpRequest);
}
Calling code sets request as below:
StudentSignUpRequest studentSignUpRequest = new StudentSignUpRequest();
//setter methods
Request payload = new Request();
payload.setData(studentSignUpRequest);
This is the request I am sending:
For more than 64 chars for firstName:
Sample JSON:
{
"data": {
"firstName": "student111111111111111111111111111111111111111111111111111111111111",
"lastName": "somesurname",
"email": "developer#gmail.com"
}
}
Where first name not included:
{
"data": {
"lastName": "somesurname",
"email": "developer#gmail.com"
}
}
Here both #Size as well as #NotNull annotation not working.
Any solution?
Validation would've worked if the Request class was like;
public class Request {
#Valid
StudentSignUpRequest data;
// other stuff
}
The fact that you have no class type for data makes it impossible for validation to be applied on it, ignoring the fact that there isn't even a #Valid annotation on the field. The #Valid annotation is used to propagate the validation cascade.
But since you cannot modify Request object, let's continue with another way to handle validation without doing it manually.
Another way is to trigger validation after you get the StudentSignUpRequest from request object;
StudentSignUpRequest signUpRequest = request.getData(StudentSignUpRequest.class);
loginRegistrationService.signUpStudent(signUpRequest) // validation will trigger with this call
What you can do is as follows;
#Service
#Validated
public class LoginRegistrationService {
public void signUpStudent(#Valid StudentSignUpRequest signUpRequest) {
// some logic
}
}
with #Validated annotation, you will activate the validation check for any #Valid annotated args in public methods within that class.
Can be used with method level validation, indicating that a
specific class is supposed to be validated at the method level (acting
as a pointcut for the corresponding validation interceptor)
This can be costly since you'd want to get any constraint violation as soon as possible without doing any costly jobs for an already doomed request.
No validation will work the way you are using it, you need to put #valid on the object inside your request object, but since you dont have control on that class the other way around is extend Request object and override getData method and apply #valid on that method, it should work that way.
First use #NotEmpty, #Notblank for Strings. Then ensure you import javax.validation.constraints not that of hibernate. If you are using a custom validator, you will need (final BindingResult bindingResult) as part of your controller method variable.
So you can use below code for validating the same.
public <T> T getData() throws ClassCastException, SomeCustomValidationException {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set s = validator.validate(this.data);
//throw SomeCustomValidationException if set is not empty else return this.data
}
A couple of things here:
The type Object for data in Request class makes it impossible for the validator to know that it is of type StudentSignUpRequest. So change the data type.
public class Request {
StudentSignUpRequest data;
Map<String, Object> meta;
}
Secondly, though you have added #Valid in the controller method, in order to validate fields in StudentSignUpRequest you have to add #Valid here as well. Now, data will be validated if passed in the API request. In case it is absent validation won't take place. If you want to make data to be mandatorily passed add #NotNull as well.
public class Request {
#Valid
#NotNull
StudentSignUpRequest data;
Map<String, Object> meta;
}
did you add following dependency?
spring-boot-starter-validation
also check https://www.baeldung.com/spring-boot-bean-validation
Related
I have a Java Pojo class which is expected as a #RequestBody in my Rest Controller. This Java Pojo class has an Integer field.
When user is calling the Rest API and they pass Integer field value as a Junk String, then Jackson is throwing InvalidFormatException. I instead want to use Javax Validator framework annotation to handle this error and show a meaningful error message in response. Unfortunately Jackson deserialization from JSON to Java happens before Javax validation, therefore my #Digits validation never gets invoked.
Note, #NotNull gets invoked but #Digits is not getting invoked because jackson fails the request much before the call reaches Javax validation layer IMO.
Java Pojo class:
public class Board implements Serializable{
private static final long serialVersionUID = 1L;
#Digits(message = "quantity must be a number", integer = 8, fraction = 0)
private Integer quantity;
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity= quantity;
}
}
Controller class:
#Controller
#RequestMapping("boards")
public class EnrichController {
#Autowired
private BoardService boardService;
#RequestMapping(value = "", method = RequestMethod.PUT, produces = "application/json;charset=UTF-8")
public #ResponseStatus(value = HttpStatus.NO_CONTENT) #ResponseBody void updateBoard(
#RequestBody #Valid Board board) throws IllegalArgumentException, MalformedURLException {
boardService.updateUserBoard(board);
}
}
User input:
{
"quantity": "abcdef"
}
As you can see I am using Javax validation annotations #Digits & #Valid but no use because Jackson Deserialization fails while parsing the Integer field quantity.
Is there anyway you can help me to solve this situation by handling this use-case using Javax validation annotations? I reckon changing field type to String in POJO class is an expensive effort as we have to do String to Integer conversion everytime I need some business logic on that field, therefore that is not an option for me.
Well in your case the problem is that you're not conforming to the REST API exposed and try to send a string instead of a number for the quantity field. This should never happen either by you or a third party service that uses your API.
Is there anyway you can help me to solve this situation by handling this use-case using Javax validation annotations?
In any case, if you still want to fix the above, a solution will be to change "quantity" in a string type field and and added pattern matching for it
#Pattern(message = "quantity must be a number", regexp="^[0-9]*$")
private String quantity;
I would like to know if there is some way to filter the properties of an object that a resource receives.
Let me explain myself: lets say we have this object:
UserDTO
public final class IniciativaDocument {
private String id;
private String name;
private String surname;
private String address;
private Double balance;
}
And for a resource I only need the properties "name" and "surname" but for other I only need "address" and "id". Is there any way to make the unneeded variables null automatically with something like Jackson?
My goal for this is that if the client sends a request with a field that isn't needed that I don't have to make it null before saving it to the database because that field is not supposed to be initialized when registering.
Thanks.
EDIT 1
For more clarity: this is for a matter of security and easiness.
Imagine I am a hacker and somehow know the fields a DTO class has. I could easily send a POST request like this to a service which is to register users:
{
"id": "wrong",
"name": "Wrong",
"balance": 20000
}
For my service I would only need id and name but the hacker is sending a balance field too.
What I want to do is that as I receive this object, I can set my endpoint /api/v1/users (which is to register) to put any initialized field that isn't id or name to null.
You might say I could just make sure it's 0 or set it to null manually. Yeah, that's true, but I want to find out if there's an automatic and more comfortable way of doing this with annotations or something similar.
EDIT 2
Resource example:
#Component
#Path("iniciativas")
#Produces(MediaType.APPLICATION_JSON)
public final class IniciativasEndpoint {
#POST
public Response crearIniciativa(#Valid #NotNull(message = CONSTRAINT_BODY_NOT_NULL) #ConvertGroup(to = IniciativasGroup.Create.class)
final IniciativaDTO iniciativaDTO,
#Context UriInfo uriInfo) {
return Response.ok().build();
}
}
You can use Jackson's JsonIgnore Property for this like:
#JsonInclude(Include.NON_NULL)
public final class IniciativaDocument {
private String id;
private String name;
private String surname;
private String address;
}
So Basically what it does is, when it is mapping this class to Json it will ignore all fields with null value. And if you want it not to apply on whole class you can do it on fields as well.
AND If you want to achieve it through front-end then you can use this great api GraphQL, so basically in request you will specify what fields you require and it will return only those fields.
Use a post-matching filter:
You can implement ContainerRequestFilter and annotate your Filter with #PostMaching (javax.ws.rs.container.PreMatching) and manipulate the your request with its data there.
Define an #interface for your Filter:
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
public #interface AnnotationForYourFilter {}
Annotate the sub-resource you want with #AnnotationForYourFilter and you are done.
I'm using Spring boot 1.4.0, Consider below code in a #RestController, what I expect is, the server side will receive a http body with form_urlencoded content type, but unfortunately it demands me a query parameter type with email and token. What's the problem here and how to fix?
#DeleteMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void removeAdmin(#RequestParam(value = "email") String email, #RequestParam(value = "token") String token) {
//...
}
#DeleteMapping is only a convenience extension the provides #RequestMapping(method=DELETE) It will not handle request paramters. You will still have to map those in the controllers method signature if you need the data to perform the work.
Since you want a body, You could create an object and mark it as #RequestBody:
public class DeleteBody {
public String email;
public String token;
}
public void removeAdmin(#RequestBody DeleteBody deleteBody) {
...
}
I offer a REST webservice using spring #RestController.
How should in general invalid parameter content be handled? I tried throwing a custom exception, but that will lead to a HTTP 500 error on the client side and expose the stacktrace thereby.
Probably that is not the right way. But how should just simple error messages be returned? (the webservice will not be accessed manually by users. Just by other services connecting to the rest controller).
Im using jersey and this is a simple example that will use the hibernate bean validation framework to validate your beans. This is a work in progress but you should can see how it will work very simply.
#Path("customers")
public class CustomerResource {
#PUT
public Response createCustomer(Customer customer) {
BeanValidator.validate(customer);
final String rialtoId = customerProvider.createCustomer(customer);
return Response.ok(rialtoId).build();
}
}
Here is a generic class that I created that handles the bean validation.
public class BeanValidator {
/**
* Used to validate an order request and all the attached objects that
* support validation.
*
* #param request
* #throws ConstraintViolationException
*/
public static <T> void validate(T request) throws ConstraintViolationException {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<T>> constraintViolations = validator.validate(request);
if (constraintViolations.size() > 0) {
throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(constraintViolations));
}
}
}
#XmlRootElement
public class Customer {
#NotNull(message = "spCustomerID1 is a required field")
#Size(max = 60, message = "spCustomerID1 has a max length of 60 characters")
private String spCustomerID1;
#Size(max = 60, message = "spCustomerID2 has a max length of 60 characters")
private String spCustomerID2;
#Size(max = 60, message = "spCustomerID3 has a max length of 60 characters")
private String spCustomerID3;
#NotNull(message = "customerName is a required field")
#Size(max = 60)
private String customerName;
#Valid
#NotNull(message = "customerAddress is a required field")
private PostalAddress customerAddress;
#Valid
#NotNull(message = "customerContact is a required field")
private ContactInfo customerContact;
#Valid
#NotNull(message = "technicalContact is a required field")
private ContactInfo technicalContact;
... / Getters and Setters
}
Then here is a simple ExceptionMapper that will support constructing a simple response to be sent back to the client. Notice that it will set the Response type to a 400 BAD_REQUEST instead of a 500+ Server Side error.
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
public Response toResponse(ConstraintViolationException exception) {
final StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> cv : exception.getConstraintViolations()) {
strBuilder.append(cv.getPropertyPath().toString() + " " + cv.getMessage());
}
RestResponse responseEntity = RestResponse.responseCode(ResponseCode.CONSTRAINT_VIOLATION).setResponseMessage(strBuilder.toString()).build();
return Response.status(Response.Status.BAD_REQUEST).entity(responseEntity).build();
}
}
This code hasn't been tested yet but it might help to get some ideas of how to do the validation. This is a pretty straight forward way to do rest service validation in my opinion and allows you to report exact variable paths along with customized error messages for each field.
You should validate your parameters at the very outmost layer of your application before it gets handed off inside your domain. At this point you're still in the HTTP layer so can take the appropriate action which is to return a 400 BAD REQUEST status.
Within that you have complete control over how to relay this information to your users (or other services). Plain text is fine if you're just logging it, or design your own Json/Xml payload describing the error.
if i understand you well,
then , generally i think it's good to have a key in each json response (or even if your response is XML), that indecates the status of the process. this field could be called status.
so each and every response you send back should have this status field, and it's value should indicate what happens while processing, and what should the caller expect in the response.
the value could be a number, or a text message, some constant-like message
also you can add another field, message that contains some text-desc of the status code.
now you have to make a list of possible statues that your service may send back.
ex:
status: 0000
message: success
status: 0001
message: invalid_params
status: 0002
message: invalid_param_value
status: 0003
message: missing_param,
:
:
etc
so your json response will contain those fields always. among the other data supposed to be returned.
now it's the clients duty to handle those responses.
JSON example:
{
"status":"0000",
"message":"success",
"images":[ ... ]
}
{
"status":"0003",
"message":"missing_param"
}
as you notice in case of non 0000 status, no other data is sent back.
just telling the client we have "this issue".
or you can make it more informative, by adding : to the error message constant, telling more info about the error:
ex,
{
"status":"0003",
"message":"missing_param:album_id"
}
telling the user, there is a missing parameter, and it's album_id
now you can write all possible status responses, and there message
this will be part of your service documentation.
I you manually validate your arguments, you could throw a specific exception. Then you can map your exception to a specific HTTP status, for instance BAD REQUEST.
You can map your exception to a response status with Spring Controller Advice: http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc.
I've a form I want to validate. It contains 2 Address variables. address1 has always to be validated, address2 has to be validated based on some conditions
public class MyForm {
String name;
#Valid Address address1;
Address address2;
}
public class Address {
#NotEmpty
private String street;
}
my controller automatically validates and binds my form obj
#RequestMapping(...)
public ModelAndView edit(
#ModelAttribute("form")
#Valid
MyForm form,
BindingResult bindingResult,
...)
if(someCondition) {
VALIDATE form.address2 USING JSR 303
the problem is that if I use the LocalValidatorFactoryBean validator i can't reuse the BinidingResult object provided by Spring. The bind won't work as the target object of 'result' is 'MyForm' and not 'Address'
validate(form.getAddress2(), bindingResult) //won't work
I'm wondering what's the standard/clean approach to do conditional validation.
I was thinking in programmatically create a new BindingResult in my controller.
final BindingResult bindingResultAddress2 = new BeanPropertyBindingResult(address2, "form");
validate(form.getAddress2(), bindingResultAddress2);
but then the List of errors I obtain from bindingResultAddress2 can't be added to the general 'bindingResult' as the field names are not correct ('street' instead of 'address2.street') and the binding won't work.
Some dirty approach would be to extend BeanPropertyBindingResult to accept some string to append to the fields name.. do you have a better approach?
The standard approach for validating hierarchical structures is to use pushNestedPath()/popNestedPath(), though I'm not sure how it plays with JSR-303:
bindingResult.pushNestedPath("address2");
validate(form.getAddress2(), bindingResult);
bindingResult.popNestedPath();
I've never tried myself, but I think the correct approach is using validator groups.
First of all, let's see #javax.validation.Valid API
Mark an association as cascaded. The associated object will be validated by cascade.
When Spring framework uses #Valid as a marker to validate its command objects, it corrupts its purpose. Spring should instead create your own specific annotation which specifies the groups which should be validated.
Unfortunately, you should use Spring native Validator API if you need to validate some groups
public void doSomething(Command command, Errors errors) {
new BeanValidationValidator(SomeUserCase.class, OtherUserCase.class)
.validate(command, errors);
if(errors.hasErrors()) {
} else {
}
}
BeanValidationValidator can be implemented as
public class BeanValidationValidator implements Validator {
javax.validation.Validator validator = ValidatorUtil.getValidator();
private Class [] groups;
public BeanValidationValidator(Class... groups) {
this.groups = groups;
}
public void validate(Object command, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(command, groups);
for(ConstraintViolation<Object> constraintViolation: constraintViolationSet) {
errors.rejectValue(constraintViolation.getPropertyPath().toString(), null, constraintViolation.getMessage());
}
}
}