How to use Hibernate validation annotations with enums?
Note you can also create a validator to check a String is part of an enumeration.
public enum UserType { PERSON, COMPANY }
@NotNull
@StringEnumeration(enumClass = UserCivility.class)
private String title;
@Documented
@Constraint(validatedBy = StringEnumerationValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, CONSTRUCTOR })
@Retention(RUNTIME)
public @interface StringEnumeration {
String message() default "{com.xxx.bean.validation.constraints.StringEnumeration.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass();
}
public class StringEnumerationValidator implements ConstraintValidator<StringEnumeration, String> {
private Set<String> AVAILABLE_ENUM_NAMES;
@Override
public void initialize(StringEnumeration stringEnumeration) {
Class<? extends Enum<?>> enumSelected = stringEnumeration.enumClass();
//Set<? extends Enum<?>> enumInstances = EnumSet.allOf(enumSelected);
Set<? extends Enum<?>> enumInstances = Sets.newHashSet(enumSelected.getEnumConstants());
AVAILABLE_ENUM_NAMES = FluentIterable
.from(enumInstances)
.transform(PrimitiveGuavaFunctions.ENUM_TO_NAME)
.toSet();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ( value == null ) {
return true;
} else {
return AVAILABLE_ENUM_NAMES.contains(value);
}
}
}
This is nice because you don't loose the information of the "wrong value". You can get a message like
The value "someBadUserType" is not a valid UserType. Valid UserType values are: PERSON, COMPANY
Edit
For those who want a non-Guava version it should work with something like:
public class StringEnumerationValidator implements ConstraintValidator<StringEnumeration, String> {
private Set<String> AVAILABLE_ENUM_NAMES;
public static Set<String> getNamesSet(Class<? extends Enum<?>> e) {
Enum<?>[] enums = e.getEnumConstants();
String[] names = new String[enums.length];
for (int i = 0; i < enums.length; i++) {
names[i] = enums[i].name();
}
Set<String> mySet = new HashSet<String>(Arrays.asList(names));
return mySet;
}
@Override
public void initialize(StringEnumeration stringEnumeration) {
Class<? extends Enum<?>> enumSelected = stringEnumeration.enumClass();
AVAILABLE_ENUM_NAMES = getNamesSet(enumSelected);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ( value == null ) {
return true;
} else {
return AVAILABLE_ENUM_NAMES.contains(value);
}
}
}
And to customize the error message and display the appropriate values, check this: https://stackoverflow.com/a/19833921/82609
I suppose a more closely related to Sebastien's answer above with fewer lines of code and makes use of EnumSet.allOf
in the expense of a rawtypes
warning
Enums setup
public enum FuelTypeEnum {DIESEL, PETROL, ELECTRIC, HYBRID, ...};
public enum BodyTypeEnum {VAN, COUPE, MUV, JEEP, ...};
Annotation setup
@Target(ElementType.FIELD) //METHOD, CONSTRUCTOR, etc.
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.class)
public @interface ValidateEnum {
String message() default "{com.xxx.yyy.ValidateEnum.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> targetClassType();
}
Validator setup
public class EnumValidator implements ConstraintValidator<ValidateEnum, String> {
private Set<String> allowedValues;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void initialize(ValidateEnum targetEnum) {
Class<? extends Enum> enumSelected = targetEnum.targetClassType();
allowedValues = (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 value == null || allowedValues.contains(value)? true : false;
}
}
Now go ahead and annotate your fields as follow
@ValidateEnum(targetClassType = FuelTypeEnum.class, message = "Please select ...."
private String fuelType;
@ValidateEnum(targetClassType = BodyTypeEnum.class, message = "Please select ...."
private String bodyType;
The above assumes you have the Hibernate Validator
setup and working with default annotation.
@NotBlank
Validate that the annotated string is not null or empty. The difference to NotEmpty is that trailing whitespaces are getting ignored.
Where as UserRole
is not String and an object
Use @NotNull
The annotated element must not be null. Accepts any type.