I've run into a situation where I need to validate a field inside an object conditionally. More specifically, I have one class PhoneType which contains two fields
#Getter
#Setter
public class PhoneType {
#JsonProperty("#CountryCode")
private String countryCode;
#JsonProperty("#Number")
private String number;
}
The class PhoneType is used in three places,
#Getter
#Setter
class PersonContact {
#JsonProperty("Mobile")
private PhoneType mobile;
#JsonProperty("WorkPhone")
private PhoneType workPhone;
#JsonProperty("OfficeFax")
private PhoneType officeFax;
}
However, with mobile, there should be an additional validation rule applied to the number field. The number must be a number with length of 10.
I have two possible solutions in mind:
Create a custom annotation to validate number for mobile
Validate number using Jackson's StdConverter
Here are the implementation of both solutions
public class ContactConverter extends StdConverter<PersonContact, PersonContact> {
#SneakyThrows
#Override
public PersonContact convert(PersonContact personContact) {
boolean validMobilePhone = Pattern.compile("\\d{10}")
.matcher(relatedPersonContact.getMobileNumber())
.matches();
if (BooleanUtils.isFalse(validMobilePhone)) {
var errorMessage = String.format(INVALID_MOBILE_NUMBER, personContact.getMobileNumber());
throw new JsonParseException(null, errorMessage);
}
return personContact;
}
}
Converter is used like this
#Getter
#Setter
#JsonDeserialize(converter = ContactConverter.class)
public class PersonContact {
#JsonProperty("#Email")
private String email;
#JsonProperty("WorkPhone")
private PhoneType workPhone;
#JsonProperty("Mobile")
private PhoneType mobile;
}
This is the code for custom annotation, however it's not working
#Retention(RUNTIME)
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Constraint(validatedBy = MobilePhoneNumberValidator.class)
#interface Phone {
String format() default "";
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
static class MobilePhoneNumberValidator implements ConstraintValidator<Phone, PhoneType> {
#Override
public void initialize(Phone constraintAnnotation) {
System.out.println("test");
}
#Override
public boolean isValid(PhoneType phoneType, ConstraintValidatorContext context) {
System.out.println("test again");
return false;
}
}
and this is how I use it
#Phone(format = "\\d{10}")
#JsonProperty("Mobile")
private PhoneType mobile;
However, the code inside the Validator is not executed.
I wonder if there is anything wrong with the custom annotation. This is SpringBoot 2.3.0, I can't think of any other reason why the custom annotation is not working.
Please help if you know there is a legit way handling dynamic annotation in Java, or you know why the above code isn't working, or you know a legit way of validating object's field just by name.
EDIT 1:
It seems like due to my poor way of explanation, there is misunderstanding.
https://www.baeldung.com/javax-validation-groups , this doesn't work in this case, the validation is applied only with the declaration of the PhoneType property in other classes (PersonContact)
I have two possible solutions, custom annotation and Jackson's converter.
I have successfully applied the converter but couldn't make the custom annotation work.
My custom annotation should be run after #JsonProperty, because it needs to have the field PhoneType mobile number to be deserialized.
Related
I have the following controller:
public interface SaveController {
#PostMapping(value = "/save")
#ResponseStatus(code = HttpStatus.CREATED)
void save(#RequestBody #Valid SaveRequest saveRequest);
}
SaveRequest corresponds to:
public class SaveRequest {
#NotNull
private SaveType type;
private String name;
}
and SaveType:
public enum SaveType {
DAILY, WEEKLY, MONTHLY;
}
The controller does not receive the enum itself, but a camelCase String. I need to convert that String into the corresponding enum. For instance:
daily should become DAILY.
weekly should become WEEKLY.
monthly should become MONTHLY.
Any other String should become null.
I've tried using the Spring Converter class, which does not work when the enum is inside an object (at least I don't know how to make it work in such times).
I honestly don't know what else to try
https://www.baeldung.com/jackson-serialize-enums
This site should probably give you plenty of options.
Best is probably something like this:
public enum SaveType {
DAILY, WEEKLY, MONTHLY;
#JsonCreator
public static SaveType saveTypeforValue(String value) {
return SaveType.valueOf(value.toUpperCase());
}
}
What you require is to have custom annotation with a custom validation class for Enum.
javax.validation library doesn't have inbuilt support for enums.
Validation class
public class SaveTypeSubSetValidator implements ConstraintValidator<SaveTypeSubset, SaveType> {
private SaveType[] subset;
#Override
public void initialize(SaveTypeSubset constraint) {
this.subset = constraint.anyOf();
}
#Override
public boolean isValid(SaveType value, ConstraintValidatorContext context) {
return value == null || Arrays.asList(subset).contains(value);
}
}
interface for validation annotation with validation message
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = SaveTypeSubSetValidator.class)
public #interface SaveTypeSubset {
SaveType[] anyOf();
String message() default "must be any of {anyOf}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Usage
#SaveTypeSubset(anyOf = {SaveType.NEW, SaveType.OLD})
private SaveType SaveType;
This is one way. More ways are mentioned in this article.
We have SpringBoot application.
For our pojo's we want to create a custom #ToLowerCase annotation which converts the field variable value to lower case.
Eg:
#Data
Employee {
private String name;
#ToLowerCase
private String emailId;
private String gender;
private String phoneNumber;
}
So my custom #ToLowerCase annotation should convert emailId to lower case.
We want to use this annotation on all kind of Pojos, whether it is rest request pojo or JPA entity pojo.
I have gone through posts on many forums but didn't get any appropriate solution for same.
Is it possible to create such annotation in Spring Boot? If yes then how?
Kindly help
Thanks
Create a custom converter: ToLowerCaseConverter.
public class ToLowerCaseConverter extends StdConverter<String, String> {
#Override
public String convert(String value) {
if (value == null){
return null;
}
return value.toLowerCase();
}
}
After create a new annotation: ToLowerCase. It works for both incoming and outgoing Strings (#JsonDeserialize/#JsonSerialize).
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonSerialize(converter = ToLowerCaseConverter.class)
#JsonDeserialize(converter = ToLowerCaseConverter.class)
public #interface ToLowerCase {
}
Finally, your example will work as intended:
#Data
Employee {
#ToLowerCase
private String emailId;
}
I have a DTO that looks something like this:
class VehicleDto {
private String type;
private Car car;
private Bike bike;
}
Now depending on the type, I need to validate on at least one of Car and Bike.
Both cannot be present in the same request.
How can I do that?
Having two fields in class, while only one of them can present, seems like a design smell for me.
But if you insist on such design - you can create a custom Validator for your VehicleDto class.
public class VehicleValidator implements Validator {
public boolean supports(Class clazz) {
return VehicleDto.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
VehicleDto dto = (VehicleDto) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "type",
"error.message.for.type.field");
if (null != dto.getType()
&& null != dto.getCar()
&& null != dto.getBike()) {
switch(dto.getType()) {
case "car":
errors.rejectValue("bike", "error.message.for.bike.field");
break;
case "bike":
errors.rejectValue("car", "error.message.for.car.field");
break;
}
}
}
}
Also, see Spring documentation about validation:
Validation using Spring’s Validator interface
Resolving codes to error messages
Injecting a Validator
For example, if we want to check whether my TaskDTO object is valid, by comparing its two attributes dueDate and repeatUntil , following are the steps to achieve it.
dependency in pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
DTO class:
#ValidTaskDTO
public class TaskDTO {
#FutureOrPresent
private ZonedDateTime dueDate;
#NotBlank(message = "Title cannot be null or blank")
private String title;
private String description;
#NotNull
private RecurrenceType recurrenceType;
#Future
private ZonedDateTime repeatUntil;
}
Custom Annotation:
#Constraint(validatedBy = {TaskDTOValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidTaskDTO {
String message() default "Due date should not be greater than or equal to Repeat Until Date.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Constraint Validator:
public class TaskDTOValidator implements ConstraintValidator<ValidTaskDTO, TaskDTO> {
#Override
public void initialize(ValidTaskDTO constraintAnnotation) {
}
#Override
public boolean isValid(TaskDTO taskDTO, ConstraintValidatorContext constraintValidatorContext) {
if (taskDTO.getRecurrenceType() == RecurrenceType.NONE) {
return true;
}
return taskDTO.getRepeatUntil() != null && taskDTO.getDueDate().isBefore(taskDTO.getRepeatUntil());
}
}
Make sure that you have #Valid in front of RequestBody of a postmapping method in your RestController. Only then the validation will get invoked:
#PostMapping
public TaskReadDTO createTask(#Valid #RequestBody TaskDTO taskDTO) {
.....
}
I hope this helps. If you need a detailed explanation on steps, have a look at this video
A little greedy question here, hope this one could also help others who want to know more about annotation validation
I am currently studying Spring, and for now, I am planning to try out the customize annotated validation.
I have searched a lot and now I know there are mainly two kinds of validations, one is used for the controller, and the other is the annotation method using #Valid
So here's my scenario:
Suppose I have two or more fields which can be null when they are ALL NULL.
But only when one of those fields contains any value except an empty string, those fields are required to have input. And I had two ideas but didn't know how to implement them correctly.
Here's the Class Example:
public class Subscriber {
private String name;
private String email;
private Integer age;
private String phone;
private Gender gender;
private Date birthday;
private Date confirmBirthday;
private String birthdayMessage;
private Boolean receiveNewsletter;
//Getter and Setter
}
Suppose I want that the birthday and confirmBirthday field need to be both null or the oppose, I may want to annotate them using one annotation for each of them and looks like this:
public class Subscriber {
private String name;
private String email;
private Integer age;
private String phone;
private Gender gender;
#NotNullIf(fieldName="confirmBirthday")
private Date birthday;
#NotNullIf(fieldName="birthday")
private Date confirmBirthday;
private String birthdayMessage;
private Boolean receiveNewsletter;
//Getter and Setter
}
So i do need to create the validation Annotation like this:
#Documented
#Constraint(validatedBy = NotNullIfConstraintValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD, ElementType.FIELD })
public #interface NotNullIf {
String fieldName();
String message() default "{NotNullIf.message}";
Class<?>[] group() default {};
Class<? extends Payload>[] payload() default {};
}
And After that i will need to create the Validator itself:
public class NotNullIfConstraintValidator implements ConstraintValidator<NotNullIf, String>{
private String fieldName;
public void initialize(NotNullIf constraintAnnotation) {
fieldName = constraintAnnotation.fieldName();
}
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value == null) {
return true;
};
//TODO Validation
return false;
}
}
So how can it be achievable?
For another idea using the same Class as an example which said that i want birthday, confirmBirthday and birthdayMessdage can only be null or the oppose at the same time.
I may require to use the class annotated validation this time for cross-field validation.
Here's how i suppose to annotate the class:
#NotNullIf(fieldName={"birthday", "confirmBirthday", "birthdayMessage"})
public class Subscriber {
//Those field same as the above one
}
So when one of that field is not null, the rest of them also needs to be entered on the client size.
Is it Possible?
I have read this article: How to access a field which is described in annotation property
But I still confusing on how the annotation validation works from those elements I listed above.
Maybe I need some detail explanation on that code or even worse I may need some basic concept inspection.
Please Help!
For this you can use a type level annotation only because a field level annotation has no access to other fields!
I did something similar to allow a choice validation (exactly one of a number of properties has to be not null). In your case the #AllOrNone annotation (or whatever name you prefer) would need an array of field names and you will get the whole object of the annotated type to the validator:
#Target(ElementType.TYPE)
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = AllOrNoneValidator.class)
public #interface AllOrNone {
String[] value();
String message() default "{AllOrNone.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class AllOrNoneValidator implements ConstraintValidator<AllOrNone, Object> {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private String[] fields;
#Override
public void initialize(AllOrNone constraintAnnotation) {
fields = constraintAnnotation.value();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
long notNull = Stream.of(fields)
.map(field -> PARSER.parseExpression(field).getValue(value))
.filter(Objects::nonNull)
.count();
return notNull == 0 || notNull == fields.length;
}
}
(As you said you use Spring I used SpEL to allow even nested fields access)
Now you can annotate your Subscriber type:
#AllOrNone({"birthday", "confirmBirthday"})
public class Subscriber {
private String name;
private String email;
private Integer age;
private String phone;
private Gender gender;
private Date birthday;
private Date confirmBirthday;
private String birthdayMessage;
private Boolean receiveNewsletter;
}
Consider adding compile-time validation for the field names. For example, in #Arne answer the strings "birthday" and "confirmBirthday" are not guaranteed to match actual field names at compile time. If you want to add that functionality, here's an example from my code for a slightly different example that assumes there are exactly two fields. The purpose is to assert that two fields are ordered... For example, it could be used for "beginDate" and "endDate".
public class OrderedValidator extends AbstractProcessor implements ConstraintValidator<Ordered, Object>
{
private String field1;
private String field2;
private Messager messager;
public void initialize(Ordered constraintAnnotation)
{
this.field1 = constraintAnnotation.field1();
this.field2 = constraintAnnotation.field2();
}
#Override
public synchronized void init(ProcessingEnvironment processingEnv)
{
super.init(processingEnv);
messager = processingEnv.getMessager();
}
#SuppressWarnings("unchecked")
public boolean isValid(Object value, ConstraintValidatorContext context)
{
Object field1Value = new BeanWrapperImpl(value).getPropertyValue(field1);
Object field2Value = new BeanWrapperImpl(value).getPropertyValue(field2);
boolean valid = true;
if (field1Value != null && field2Value != null)
{
if (field1Value.getClass().equals(field2Value.getClass()))
{
valid = ((Comparable) field1Value).compareTo((Comparable) field2Value) <= 0;
}
}
return valid;
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
{
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Ordered.class))
{
if (annotatedElement.getKind() != ElementKind.CLASS)
{
messager.printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with " + Ordered.class.getSimpleName());
return true;
}
TypeElement typeElement = (TypeElement) annotatedElement;
List<? extends Element> elements = typeElement.getEnclosedElements();
boolean field1Found = false;
boolean field2Found = false;
for (Element e : elements)
{
if (e.getKind() == ElementKind.FIELD && field1 != null && field1.equals(e.getSimpleName()))
{
field1Found = true;
}
else if (e.getKind() == ElementKind.FIELD && field2 != null && field2.equals(e.getSimpleName()))
{
field2Found = true;
}
}
if (field1 != null && !field1Found)
{
messager.printMessage(Diagnostic.Kind.ERROR, "Could not find field named " + field1);
return true;
}
if (field2 != null && !field2Found)
{
messager.printMessage(Diagnostic.Kind.ERROR, "Could not find field named " + field2);
return true;
}
}
return false;
}
}
I'm using JPA 2.0/Hibernate validation to validate my models. I now have a situation where the combination of two fields has to be validated:
public class MyModel {
public Integer getValue1() {
//...
}
public String getValue2() {
//...
}
}
The model is invalid if both getValue1() and getValue2() are null and valid otherwise.
How can I perform this kind of validation with JPA 2.0/Hibernate? With a simple #NotNull annotation both getters must be non-null to pass validation.
For multiple properties validation, you should use class-level constraints. From
Bean Validation Sneak Peek part II: custom constraints:
Class-level constraints
Some of you have expressed concerns
about the ability to apply a
constraint spanning multiple
properties, or to express constraint
which depend on several properties.
The classical example is address
validation. Addresses have intricate
rules:
a street name is somewhat standard and must certainly have a length limit
the zip code structure entirely depends on the country
the city can often be correlated to a zipcode and some error checking can
be done (provided that a validation
service is accessible)
because of these interdependencies a simple property level constraint does
to fit the bill
The solution offered by the Bean
Validation specification is two-fold:
it offers the ability to force a set of constraints to be applied before an
other set of constraints through the
use of groups and group sequences.
This subject will be covered in the
next blog entry
it allows to define class level constraints
Class level constraints are regular
constraints (annotation /
implementation duo) which apply on a
class rather than a property. Said
differently, class-level constraints
receive the object instance (rather
than the property value) in isValid.
#AddressAnnotation
public class Address {
#NotNull #Max(50) private String street1;
#Max(50) private String street2;
#Max(10) #NotNull private String zipCode;
#Max(20) #NotNull String city;
#NotNull private Country country;
...
}
#Constraint(validatedBy = MultiCountryAddressValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface AddressAnnotation {
String message() default "{error.address}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class MultiCountryAddressValidator implements ConstraintValidator<AddressAnnotation, Address> {
public void initialize(AddressAnnotation constraintAnnotation) {
// initialize the zipcode/city/country correlation service
}
/**
* Validate zipcode and city depending on the country
*/
public boolean isValid(Address object, ConstraintValidatorContext context) {
if (!(object instanceof Address)) {
throw new IllegalArgumentException("#AddressAnnotation only applies to Address objects");
}
Address address = (Address) object;
Country country = address.getCountry();
if (country.getISO2() == "FR") {
// check address.getZipCode() structure for France (5 numbers)
// check zipcode and city correlation (calling an external service?)
return isValid;
} else if (country.getISO2() == "GR") {
// check address.getZipCode() structure for Greece
// no zipcode / city correlation available at the moment
return isValid;
}
// ...
}
}
The advanced address validation rules
have been left out of the address
object and implemented by
MultiCountryAddressValidator. By
accessing the object instance, class
level constraints have a lot of
flexibility and can validate multiple
correlated properties. Note that
ordering is left out of the equation
here, we will come back to it in the
next post.
The expert group has discussed various
multiple properties support
approaches: we think the class level
constraint approach provides both
enough simplicity and flexibility
compared to other property level
approaches involving dependencies.
Your feedback is welcome.
To work properly with Bean Validation, the example provided in Pascal Thivent's answer could be rewritten as follows:
#ValidAddress
public class Address {
#NotNull
#Size(max = 50)
private String street1;
#Size(max = 50)
private String street2;
#NotNull
#Size(max = 10)
private String zipCode;
#NotNull
#Size(max = 20)
private String city;
#Valid
#NotNull
private Country country;
// Getters and setters
}
public class Country {
#NotNull
#Size(min = 2, max = 2)
private String iso2;
// Getters and setters
}
#Documented
#Target(TYPE)
#Retention(RUNTIME)
#Constraint(validatedBy = { MultiCountryAddressValidator.class })
public #interface ValidAddress {
String message() default "{com.example.validation.ValidAddress.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class MultiCountryAddressValidator
implements ConstraintValidator<ValidAddress, Address> {
public void initialize(ValidAddress constraintAnnotation) {
}
#Override
public boolean isValid(Address address,
ConstraintValidatorContext constraintValidatorContext) {
Country country = address.getCountry();
if (country == null || country.getIso2() == null || address.getZipCode() == null) {
return true;
}
switch (country.getIso2()) {
case "FR":
return // Check if address.getZipCode() is valid for France
case "GR":
return // Check if address.getZipCode() is valid for Greece
default:
return true;
}
}
}
You can use #javax.validation.constraints.AssertTrue validations like this:
public class MyModel {
private String value1;
private String value2;
#AssertTrue(message = "Values are invalid")
private boolean isValid() {
return value1 != null || value2 != null;
}
}
A custom class level validator is the way to go, when you want to stay with the Bean Validation specification, example here.
If you are happy to use a Hibernate Validator feature, you could use #ScriptAssert, which is provided since Validator-4.1.0.Final. Exceprt from its JavaDoc:
Script expressions can be written in any scripting or expression
language, for which a JSR 223 ("Scripting for the JavaTM Platform")
compatible engine can be found on the classpath.
Example:
#ScriptAssert(lang = "javascript", script = "_this.value1 != null || _this != value2)")
public class MyBean {
private String value1;
private String value2;
}
Programing Language : Java
This is a solution that helped me.
Requirement :
On UI there is a table which contains List of Objects which is having Maping to multiple Tables/Objects with fk relationship.
Now the validation is out of multiple fks there are only 3 columns which can't be duplicated. I mean the combination of 3 can't be duplicated.
Note : As I am working on Custom Framework on Java there is no option to use HashCode or equals. If I will use array index iteration that will increase the time complexity which I don't want.
Solution:
I have prepared a String , which is a custom String that contains ID of FK1#ID of FK2#ID of FK3
Ex: the String will form like -> 1000L#3000L#1300L#
This String, we will add to a set by using add() of set which will return false if duplicate comes up.
Based on this flag we can throw validation message.
This helped me. Some scenario and restriction comes where DS may not help.