Considering the fact that some validation annotations such as @Size(min=1,max=5) allow for parameterized error messages with their annotation parameters such as The String must have a length of {min} to {max} characters., I found a solution for parameterized error messages in general with place holders such as {0}, {1}, ...:
Define an own validation constraint such as:
@Constraint(validatedBy=OnlyCharactersAllowedValidator.class)
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OnlyCharactersAllowed {
String message() default "parameterizedMessage";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Within the validator class, reflection can be used to extend a map that stores all the annotation parameters (such as min and max for @Size) and additional parameters can be added for the keys 0, 1 etc.:
public class OnlyCharactersAllowedValidator implements ConstraintValidator<OnlyCharactersAllowed,String>
{
private static final String validCharacters = "abcdefghijklmnopqrstuvwxyz";
@Override
public boolean isValid(String text, ConstraintValidatorContext constraintContext)
{
for (int index = 0; index < text.length; index++) {
String aCharacter = text.substring(index, index+1);
if (validCharacters.indexOf(aCharacter) < 0) {
/* Within this message call the magic happens: {0} is mapped to the invalid character. */
addMessageParameter(constraintContext, aCharacter);
/* Create violation message manually and suppress the default message. */ constraintContext.buildConstraintViolationWithTemplate(constraintContext.getDefaultConstraintMessageTemplate()).addConstraintViolation();
constraintContext.disableDefaultConstraintViolation();
/* No further validation: show only one error message for invalid characters. */
break;
}
}
}
private void addMessageParameter(ConstraintValidatorContext constraintContext, Object... parameter)
{
try
{
/* Get map for parameters (reflection necessary). */
Field descriptorField = ConstraintValidatorContextImpl.class.getDeclaredField("constraintDescriptor");
descriptorField.setAccessible(true);
@SuppressWarnings("unchecked")
ConstraintDescriptorImpl<OnlyCharactersAllowed> descriptor = (ConstraintDescriptorImpl<OnlyCharactersAllowed>) descriptorField.get(constraintContext);
Map<String,Object> attributes = descriptor.getAttributes();
/* Copy immutable Map to new Map. */
Map<String,Object> newAttributes = new HashMap<String,Object>(attributes.size() + parameter.length);
for (String key : attributes.keySet())
{
newAttributes.put(key, attributes.get(key));
}
/* Add given parameters to attributes. */
Integer parameterCounter = 0;
for (Object param : parameter)
{
newAttributes.put(parameterCounter.toString(), param);
parameterCounter++;
}
/* Set new Map in Descriptor (reflection necessary). */
Field attributesField = ConstraintDescriptorImpl.class.getDeclaredField("attributes");
attributesField.setAccessible(true);
attributesField.set(descriptor, newAttributes);
}
catch (NoSuchFieldException | IllegalAccessException | SecurityException | ClassCastException e1)
{
/* Do nothing in case of exception. Unparameterized message is shown. */
}
}
}
This solution even works if your validation message is defined in a properties file such as.
parameterizedMessage = Invalid character: Do not use the {0} character.