Validating date with Spring Boot or Hibernate Validator - java

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

Related

Java Request Date Validation: For any class with two variable parameters

I am creating a shared component for Request Date constraints, Begin Date is before End Date.
I want to take my current Validation request, and make it common, so I type in the (Begin and EndDate class members for any Class), and it will work. How can this be done? I use annotations above the request class, in ProductRequest below .
Note: How do I set Start and End date parameters in the annotation; they may not always be "Start/End" field members, sometimes they could be "Begin/Finish" in another class .
#DatesRequestConstraint
public class ProductRequest {
private Long productId;
private DateTime startDate;
private DateTime EndDate;
private List<String> productStatus;
}
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = ProductValidator.class)
#Documented
public #interface DatesRequestConstraint {
String message() default "Invalid dates request.";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
}
public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, ProductRequest> {
#Override
public void initialize(DatesRequestConstraint constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(ProductRequest productRequest, ConstraintValidatorContext constraintValidatorContext) {
if (productRequest.getStartDate() != null &&
productRequest.getEndDate() != null &&
productRequest.getStartDate().isAfter(productRequest.getEndDate())) {
return false;
}
else return true;
}
You can:
Implement ConstraintValidator<DatesMatch, Object> so that you can apply the #DatesMatch annotation on any type;
Add custom String fields to the #DatesMatch annotation where you can specify the names of the fields you want to validate;
Use reflection at runtime to access the field values by their specified name.
There's a similar example of class-level validation over multiple custom fields here: Baeldung: Spring MVC Custom Validation (scroll down to "9. Custom Class Level Validation").
Customized to your example, something like this should work:
#Constraint(validatedBy = DatesMatchValidator.class)
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface DatesMatch {
String message() default "The dates don't match.";
String startField();
String endField();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
DatesMatch[] value();
}
}
// Accept a list of items so that you can validate more than one pair of dates on the same object if needed
#DatesMatch.List({
#DatesMatch(
startField = "startDate",
endField = "endDate",
message = "The end date must be after the start date."
)
})
public class ProductRequest {
private Long productId;
private Instant startDate;
private Instant endDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
public class DatesMatchValidator implements ConstraintValidator<DatesMatch, Object> {
private String startField;
private String endField;
public void initialize(DatesMatch constraintAnnotation) {
this.startField = constraintAnnotation.startField();
this.endField = constraintAnnotation.endField();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
Instant startFieldValue = (Instant) new BeanWrapperImpl(value)
.getPropertyValue(startField);
Instant endFieldValue = (Instant) new BeanWrapperImpl(value)
.getPropertyValue(endField);
if (startFieldValue == null || endFieldValue == null) {
return true;
}
return endFieldValue.isAfter(startFieldValue);
}
}
Update: (in response to comment):
this answer is great, allows multiple pair of dates, however isn't type-string safe, person can type in whatever for the fields in the product fields
Implementing ConstraintValidator<DatesMatch, Object> is meant as an easy catch-all solution you can apply to any class.
But you can absolutely do it in a more type-safe way by implementing a separate ConstraintValidator for each type you want to validate (i.e. ConstraintValidator<DatesMatch, ProductRequest>, ConstraintValidator<DatesMatch, AnotherRequest>, ...) and then specify all of them in the #Constraint(validatedBy={...}) attribute:
#Constraint(validatedBy = {ProductRequestDatesMatchValidator.class, AnotherRequestDatesMatchValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface DatesMatch {
String message() default "Invalid dates request.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
#DatesMatch(message = "Start and end dates do not match!")
public class ProductRequest {
private Long productId;
private Instant startDate;
private Instant endDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
#DatesMatch(message = "Begin and finish dates do not match!")
public class AnotherRequest {
private Long productId;
private Instant beginDate;
private Instant finishDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
public class ProductRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, ProductRequest> {
#Override
public boolean isValid(ProductRequest value, ConstraintValidatorContext context) {
// No need to cast here
Instant startDate = value.getStartDate();
Instant endDate = value.getEndDate();
// You could reuse this logic between each implementation by putting it in a parent class or a utility method
if (startDate == null || endDate == null) {
return true;
}
return startDate.isBefore(endDate);
}
}
public class AnotherRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, AnotherRequest> {
#Override
public boolean isValid(AnotherRequest value, ConstraintValidatorContext context) {
Instant beginDate = value.getBeginDate();
Instant finishDate = value.getFinishDate();
if (beginDate == null || finishDate == null) {
return true;
}
return beginDate.isBefore(finishDate);
}
}
Do note, however, that this is still not compile-time type-safe, as you could put the #DatesMatch annotation on a class for which you haven't written an implementation and the validation will only fail at runtime.
(You could achieve compile-time type-safety using annotation processing, but this another topic for another time.)
You can annotate startDate and endDate with custom annotations something like:
#StartDateField
private DateTime startDate;
#EndDateField
private DateTime endDate;
Then in your isValid(), you can access both startDate and endDate fields by their annotations by iterating over all class fields (in your case, all ProductRequest fields) and checking the following:
field.isAnnotationPresent(StartDateField.class)
field.isAnnotationPresent(EndDateField.class)
The complete code could be as follows:
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.List;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({ ANNOTATION_TYPE.TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = ProductValidator.class)
#Documented
#interface DatesRequestConstraint {
String message() default "Invalid dates request.";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
}
#DatesRequestConstraint
class ProductRequest {
private Long productId;
#StartDateField
private DateTime startDate;
#EndDateField
private DateTime EndDate;
private List<String> productStatus;
}
#Target({ ElementType.FIELD })
#Retention(RUNTIME)
#Documented
#interface StartDateField {
}
#Target({ ElementType.FIELD })
#Retention(RUNTIME)
#Documented
#interface EndDateField {
}
public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, Object> {
#Override
public void initialize(DatesRequestConstraint constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(Object requestObject, ConstraintValidatorContext constraintValidatorContext) {
DateTime startDate = getDateFieldByAnnotation(requestObject, StartDateField.class);
DateTime endDate = getDateFieldByAnnotation(requestObject, EndDateField.class);
if (startDate != null &&
endDate != null &&
startDate.isAfter(endDate)) {
return false;
} else return true;
}
private DateTime getDateFieldByAnnotation(Object requestObject, Class<? extends Annotation> annotationClass) {
return Arrays.stream(requestObject.getClass().getDeclaredFields()).filter(field -> field.isAnnotationPresent(annotationClass)).map(field -> {
try {
return field.get(requestObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}).map(DateTime.class::cast).findAny().orElse(null);
}
}

Spring Boot Validation with environment variable

I would like to put the value of a spring boot environment variable into a validation annotation (#Min, #Max), but i don't know how to do it. Here is my code :
public class MessageDTO {
#Value("${validationMinMax.min}")
private Integer min;
#JsonProperty("Message_ID")
#NotBlank(message = "messageId cannot be blank.")
#Pattern(regexp = "\\w+", message = "messageId don't suits the pattern")
private String messageId;
#JsonProperty("Message_Type")
#NotBlank(message = "messageType cannot be blank")
private String messageType;
#JsonProperty("EO_ID")
#NotBlank(message = "eoId cannot be blank")
private String eoId;
#JsonProperty("UI_Type")
#NotNull(message = "uiType cannot be null")
#Min(1)
#Max(3)
private Integer uiType;
And here is my application.yml :
server:
port: 8080
spring:
data:
cassandra:
keyspace-name: message_keyspace
port: 9042
contact-points:
- localhost
validationMinMax:
min: 1
max: 3
I would like to put the field "min" and "max" of my yml into the annotation field #Min() and #Max() of my attribute uiType. Does anyone knows how to do it ? Thanks in advance for your help !
You can write your own validation annotation with a custom validator. In this validator you can autowire spring beans and inject configuration properties:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { MyValidator.class })
#Documented
public #interface MyValidationAnnotation {
String message() default "";
Class<?>[] groups() default {};
Class<? extends javax.validation.Payload>[] payload() default {};
}
The validator class:
public class MyValidator implements ConstraintValidator<MyValidationAnnotation, Integer> {
#Autowired
private MyService service;
public void initialize(MyValidationAnnotation constraintAnnotation) {
// ...
}
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if(service.validate(value)) {
return true;
} else {
return false;
}
}
}
And then use it:
#MyValidationAnnotation
Integer foo;

ConstraintValidation Not Working for a Rest Api Request

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.

How to validate non-required RequestParam is not blank?

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

javax.validation.UnexpectedTypeException using Spring ConstraintValidator for an enumeration

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

Categories