I'm using validators in my spring controllers. If #RequestParam is required there is no problem, i can check String with #NotBlank. But if #RequestParam is optional, i can't use it with #NotBlank, because this parameter is optional and sometimes can be null.
I want to validate #NotBlank if String is not null. Is there any constraint help me?
#RequestParam #NotBlank String name
working perfectly. I have problem with required=false
if client don't send optional description parameter, validation fails.
#PatchMapping("/role/{id}")
public ResponseEntity<?> updateRole(HttpServletRequest request, #PathVariable #Positive Integer id,
#RequestParam #NotBlank String name,
#RequestParam(required = false) #NotBlank String description)
I want to validate #NotBlank if description is not null.
`#RequestParam(required = false) #NotBlank String description`
If i use like that, i got "Input validation failed!".
You need to add a custom validator for this.
Interface
#Documented
#Constraint(validatedBy = YourValidator.class)
#Target({ ElementType.METHOD,ElementType.ANNOTATION_TYPE,ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface NotBlankIfPresent{
String message() default "Error MEssage";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Your Validator Class
public class YourValidator implements ConstraintValidator<NotBlankIfPresent, String> {
#Override
public boolean isValid(String s, ConstraintValidatorContext context) {
if (s == null) {
return true;
}
return !s.isBlank();
}
}
This is not right approach to validate #RequestParam. You have to validate in your code
if it is blank then throw new IllegalArgumentException("{\"error\":\"The parameter is invalid\"}"
There is no point in using #RequestParam(required = false) and #NotBlank together.
Here is how the #NotBlank annotation works.
a String field constrained with #NotBlank must be not null and the trimmed length must be greater than zero.
Maybe a workaround could be using a default value in your request param variable whenever you have required = false
Example:
#PatchMapping("/role/{id}")
public ResponseEntity<?> updateRole(HttpServletRequest request, #PathVariable
#Positive Integer id, #RequestParam #NotBlank String name,
#RequestParam(required = false, defaultValue = "adefaultvalue") #NotBlank String description) {
if(description.equals("adefaultvalue") {
// that means that the user did not send any value for this variable so you can
// add your validation logic here
}
}
Please have in mind that the above code has not been tested
Related
I am working on javax validation API in Spring Boot Application. I have a User bean and i have to validate that username given in request is unique and doesn't exist into Database.
I have created custom annotation (UniqueUser.java) and custom Validator(UniqueUserValidator.java) for this requirement.
public class User {
#NotNull
#UniqueUser
private String username;
#NotNull
private String password;
#NotNull
#Email
private String email;
#NotNull
private String phone;
}
UniqueUser.java
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = UniqueUserValidator.class)
#Documented
public #interface NameMatch
{
String message() default "User id already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueUserValidator.java
public class UniqueUserValidator implements ConstraintValidator<NameMatch, Object>
{
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String userName, final ConstraintValidatorContext context)
{
boolean isValidUser = false;
if(userName!=null && !userName.isEmpty()) {
Optional<User> user= userRepository.findByUserId(userName);
isValidUser = !user.isPresent();
}
return isValidUser;
}
}
In above code, the unique user validation get called for username field even if the field is null and shows Username already exists error message. I want the custom validator to get called only when username has some value. Is it possible to avoid this call.
I can fix the error by modifying the below method and returning true if username is null, but i want't to avoid this unnecessary call.
public class UniqueUserValidator implements ConstraintValidator<NameMatch, Object>
{
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String userName, final ConstraintValidatorContext context)
{
boolean isValidUser = false;
if(userName!=null && !userName.isEmpty()) {
Optional<User> user= userRepository.findByUserId(userName);
isValidUser = !user.isPresent();
} else {
isValidUser = true;
}
return isValidUser;
}
}
I think what you are looking for is Grouping constraints;
Groups allow you to restrict the set of constraints applied during
validation. One use case for validation groups are UI wizards where in
each step only a specified subset of constraints should get validated.
The groups targeted are passed as var-arg parameters to the
appropriate validate method.
In your case it should look like this:
#GroupSequence({ FirstConstaint.class, SecondConstaint.class })
public class User {
#NotNull(groups = FirstConstaint.class)
#UniqueUser(groups = SecondConstaint.class)
private String username;
// the rest of your fields
}
interface FirstConstaint {
}
interface SecondConstaint {
}
This way, it will check #UniqueUser only if the field is not null, otherwise, it will return the message of #NotNull validation.
Otherwise like #User said you can use this checks in service layer ^^
I have the following REST API:
#ResponseBody
#PostMapping(path = "/configureSegment")
public ResponseEntity<ResultData> configureSegment(#RequestParam() String segment,
#RequestBody #Valid CloudSegmentConfig segmentConfig
) {
CloudSegmentConfig:
#JsonProperty(value="txConfig", required = true)
#NotNull(message="Please provide a valid txConfig")
TelemetryConfig telemetryConfig;
#JsonProperty(value="rxConfig")
ExternalSourcePpdkConfig externalSourcePpdkConfig = new ExternalSourcePpdkConfig(true);
TelemetryConfig:
public class TelemetryConfig {
static Gson gson = new Gson();
#JsonProperty(value="location", required = true)
#Valid
#NotNull(message="Please provide a valid location")
Location location;
#Valid
#JsonProperty(value="isEnabled", required = true)
#NotNull(message="Please provide a valid isEnabled")
Boolean isEnabled;
Location:
static public enum Location {
US("usa"),
EU("europe"),
CANADA("canada"),
ASIA("asia");
private String name;
private Location(String s) {
this.name = s;
}
private String getName() {
return this.name;
}
}
When I'm trying to send the following JSON:
{
"txConfig": {
"location": "asdsad"
}
}
The API return empty response 400 bad request, while I expect it to validate the location to be one of the ENUMs of the class. I also expect it to validate the isEnable parameter while it doesn't although I added all possible annotation to it..
Any idea?
Use #Valid annotation on TelemetryConfig telemetryConfig and no need to use #Valid on the field of TelemetryConfig class.
#Valid
TelemetryConfig telemetryConfig;
And for enum subset validation you can create a customer validator with annotation and use it.
A good doc about this Validating a Subset of an Enum
I created an API and added an custom-annotation to validate the Request body object, but this was never getting called. Below is the Object. Please go through the code and help me out where the code need to be corrected?
#NotNull, #Size is also not working
Request Body Object
#Getter
#AllArgsConstructor
#Sample
public class SaleRequest {
#NotNull
private Integer sale;
#NotNull
private Date dateTime;
#NotNull
#Size(min = 10, max = 10)
private String customerId;
}
Annotation
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {SalesRequestValidator.class})
#Documented
public #interface Sample {
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String message() default "Invalid Sale Request";
}
Validator
public class SalesRequestValidator implements ConstraintValidator<Sample, SaleRequest> {
private String message;
#Override
public void initialize(Sample constraintAnnotation) {
this.message = constraintAnnotation.message();
}
#Override
public boolean isValid(SaleRequest sale, ConstraintValidatorContext context) {
System.out.println("Tested!");
return sale.getSale() > 0;
}
}
Api Implementation
public interface SalesApi {
#RequestMapping(
value = {"/sales"},
produces = {"application/json"},
consumes = {"application/json"},
method = {RequestMethod.POST}
)
ResponseEntity<Integer> submitSale(#RequestBody #Valid SaleRequest saleRequest);
}
Could not figure where I went wrong
Implementation looks ok.
Make sure you have set the #Valid annotation in your controller method where you expect to receive the request body of SaleRequest.
It should look something like this:
addNewSaleRequest(#RequestBody #Valid SaleRequest saleRequest)
Try to extend #Target({ElementType.TYPE}) with ElementType.PARAMETER as you want to validate a method parameter with it.
A #Validated annotation is needed on the related Controller as well.
I have implemented a ConstraintValidator in order to valide a DTO that contains an enumeration. I followed this Spring documentation for that.
This is the custom annotation to be applied to the enum field:
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR,
ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EnumValidator.class)
public #interface ValidEnum {
String message() default "{com.test.validation.constraints.ValidEnum}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> target();
}
The EnumValidator looks like this:
public class EnumValidator implements ConstraintValidator<ValidEnum,String> {
private Set<String> enumValues;
#Override
public void initialize(ValidEnum targetEnum) {
Class<? extends Enum> enumSelected = targetEnum.targetClassType();
enumValues = (Set<String>) EnumSet.allOf(enumSelected).stream()
.map(e -> ((Enum<? extends Enum<?>>) e).name()).collect(Collectors
.toSet());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return enumValues.contains(value);
}
}
This is the enum:
public enum Gender {
MALE,FEMALE;
}
This is the DTO to validate:
public final class UserDTO{
#ValidEnum(target = Gender.class)
private Gender gender;
#NotEmpty
#Max(100)
private String fullName;
}
And the controller that is validating the field:
#Controller
public class RegistrationController {
private static final String REGISTER_USER = "/register";
private final RegistrationService registrationService;
#PostMapping(value = REGISTER_USER)
#Consumes(APPLICATION_JSON)
#Produces(APPLICATION_JSON)
public UserRegistrationResponse register(#Valid UserDTO userRegistrationRequest) {
return registrationService.register(userRegistrationRequest);
}
}
It seems that Spring is not detecting the validator, because it throws this exception:
org.springframework.web.util.NestedServletException: Request processing failed;
nested exception is javax.validation.UnexpectedTypeException: HV000030:
No validator could be found for constraint 'com.test.ws.web.validation.ValidEnumType' validating type 'com.test.ws.domain.model.Gender'. Check configuration for 'gender'
I am using Spring boot 2.0.4 which include the required dependencies for validation.
Any idea why it fails?
You have three mistakes in your code.
#Max(100) this annotation shouldn't be applied to String field! only for numeric type. If you need to specify String size range restrictions you may use #Size(min = 2, max = 250) annotation.
You forgot RequestBody annotation in the controller method signature:
register(#Valid #RequestBody UserDTO userRegistrationRequest)
Enum validation consume String value from Rest API, not Enum itself. That's why you've got an error here.
I've created test project for you. Please check it here https://github.com/alex-petrov81/stackoverflow-answers/tree/master/enum-validator
I've built a REST Service using Spring Boot. I'm also using Hibernate Validator to validate data. I have a REST endpoint like this:
#PostMapping(value = "${apiVersion.v_1}" + "/parameter-dates")
public ResponseEntity createParameterDate( #RequestBody ParameterDate parameterDate){
// Some code that use parameterDate
}
ParameterDate is defined in a class like this:
public class ParameterDate {
#NotNull(message = "Parameter Date Unadjusted can not be blank or null")
private Date parameterDateUnadjusted;
#NotNull(message = "Parameter Date Adjusted can not be blank or null")
private Date parameterDateAdjusted;
private Date parameterDateAdded;
private Date parameterDateChanged;
}
I would like to validate parameterDateUnadjusted and parameterDateAdjusted to make sure both of them are valid dates. I've tried with #DateTimeFormat(pattern = "yyyy-MM-dd") but it won't give me a validation error for not validate as long as they stick to yyyy-MM-dd. One example would be 2017-01-40 that it just interpret as 2017-02-09. I guess #DateTimeFormat is rather a formatter than a validator. I also tried using Hibernate Validator's #Pattern and rexexp like #Pattern(regexp="\\t(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\\d\\d\\t"). But this gives me the error
V000030: No validator could be found for constraint 'javax.validation.constraints.Pattern' validating type 'java.util.Date'. Check configuration for 'parameterDateAdjusted'
Any suggestion how I can validate these dates?
Here is an example to implement validator for Date object:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyDateValidator.class)
#Documented
public #interface ValidDate {
String message() default "some message here";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class MyDateValidator implements ConstraintValidator<ValidDate, Date> {
public void initialize(ValidDate constraint) {
}
public boolean isValid(Date value, ConstraintValidatorContext context) {
// validate the value here.
}
}