How to create custom Validation Messages based on an annotation property?
Solution 1
If your requirement can be satisfied by interpolating hibernate messages, so you can create/name your *property file like that:
ValidationMessages.properties
And inside that:
javax.validation.constraints.NotNull.message = CUSTOMIZED MESSAGE WHEN NOTNULL is violated!
Hibernate by default searches a ResourceBundle
named ValidationMessages
. Also a locale might be involved: ValidationMessages_en
, ValidationMessages_de
, <..>
Hibernate will provide your customized message through interpolatedMessage
parameter, so all ConstraintViolationException
relevant information (included your message ) will be showed. So you message will be a part of real exception. Some unwieldy information will be provided!
If you want to make your custom exception (without default ConstraintViolationException
behavior) check this out:
Using GenericDao
concept, consider the following
public void saveOrUpdate(IEntity<?> entity) {
try {
if(entity.getId == null) {
em.persist(entity);
} else {
em.merge(entity)l
}
} catch(ConstraintViolationException cve) {
throw new ConstraintViolationEx(constructViolationMessage(cve.getConstraintViolations()));
}
}
private String constructMessage(Set<ConstraintViolation<?>> pConstraintViolations) {
StringBuilder customMessages = new StringBuilder();
for(ConstraintViolation<?> violation : pConstraintViolations) {
String targetAnnotation = violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName();
if(supportsCustomMessage(targetAnnotation)) {
applyMessage(violation, targetAnnotation, customMessages);
} else {
// do something with not customized constraints' messages e.g. append it to existing container
}
}
return customMessages.toString();
}
private void applyMessage(ConstraintViolation<?> pViolation, String pTargetAnnotation, StringBuilder pCustomMessages) {
String targetClass = pViolation.getRootBean().getClass().getName();
String targetField = pViolation.getPropertyPath().toString();
pCustomMessages.append(MessageFormat.format(getMessageByAnnotation(pTargetAnnotation), targetClass, targetField));
pCustomMessages.append(System.getProperty("line.separator"));
}
private String getBundleKey() {
return "ValidationMessages"; //FIXME: hardcoded - implement your true key
}
private String getMessageByAnnotation(String pTargetAnnotation) {
ResourceBundle messages = ResourceBundle.getBundle(getBundleKey());
switch(pTargetAnnotation) {
case "NotNull":
return messages.getString(pTargetAnnotation + ".message");
default:
return "";
}
}
private boolean supportsCustomMessage(String pTargetAnnotation) {
return customizedConstraintsTypes.contains(pTargetAnnotation);
}
Produced result:
test.model.exceptions.ConstraintViolationEx
test.model.Person : name cannot be null
test.model.Person : surname cannot be null
A hibernate ConstraintViolation
provides relevant information about root class
and restricted field
. As you see, it applies for all hibernate supported constraints, so you need to check if current annotation can be customized by supportsCustomMessage(<..>)
! If it can (it's up to you), you should get appropriate message by constraint annotation doing `getMessageByAnnotation(<..>)'.
All you need to do is implement not supported constraints logic. For example it can append it's cause message or interpolated with default message (and true exception goes to *log file)
Solution 2
To customize your annotation message you need to disable the existing violation message inside isValid() method and build a new violation message and add it.
constraintContext.disableDefaultConstraintViolation();
constraintContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
In the given below example, I am creating an annotation for input date validation on the basis of "invalid date", "can't be greater than today date" and "date format is correct or not".
@CheckDateIsValid(displayPattern = "DD/MM/YYYY", programPattern = "dd/MM/yyyy", groups = Order2.class)
private String fromDate;
Annotation Interface -
public @interface CheckDateIsValid {
String message() default "default message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String displayPattern();
String programPattern();
}
Annotation implementation Class -
public class CheckDateIsValidValidator implements ConstraintValidator<CheckDateIsValid, String> {
@Value("${app.country.timeZone}")
private String timeZone;
private String displayPattern;
private String programPattern;
@Override
public void initialize(CheckDateIsValid constraintAnnotation) {
this.displayPattern = constraintAnnotation.displayPattern();
this.programPattern = constraintAnnotation.programPattern();
}
@Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
try {
// disable existing violation message
constraintContext.disableDefaultConstraintViolation();
if (object == null) {
return true;
}
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(programPattern);
LocalDateTime time = LocalDate.parse(object, formatter).atStartOfDay();
ZoneOffset zoneOffSet = ZoneOffset.of(timeZone);
OffsetDateTime todayDateTime = OffsetDateTime.now(zoneOffSet);
if (time == null) {
customMessageForValidation(constraintContext, "date is not valid");
return false;
} else if (todayDateTime.isBefore(time.atOffset(zoneOffSet))) {
customMessageForValidation(constraintContext, "can't be greater than today date");
return false;
}
return time != null;
} catch (Exception e) {
customMessageForValidation(constraintContext, "date format should be like " + displayPattern);
return false;
}
}
private void customMessageForValidation(ConstraintValidatorContext constraintContext, String message) {
// build new violation message and add it
constraintContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
}
}
chr0x
Updated on June 13, 2022Comments
-
chr0x almost 2 years
I'm using the Hibernate @NotNull validator, and I'm trying to create custom messages to tell the user which field has generated the error when it is null. Something like this:
notNull.custom = The field {0} can't be null.
(this will be in my ValidationMessages.properties file).
Where the {0} should be the field name passed to the validator this way:
@NotNull(field="field name")
There is any way I can do that?