I have this web application where I have setup bean validation and it works fantastically well. But for one field, it doesn't read the message that I have setup in my validation.properties file. For other fields, it works. I am perplexed as to why this happens? Let me show you my setup:
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setCacheSeconds(0);
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
messageSource.setBasenames("/WEB-INF/Validation/validation"); //I have tried setBaseName as well but same difference.
return messageSource;
}
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(this.messageSource());
return validator;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(this.localValidatorFactoryBean());
return processor;
}
#Autowired
SpringValidatorAdapter validator; //global validator
#Override
public Validator getValidator() {
return this.validator;
}
My properties file is in correct location. I know that because for some errors, messages are read from this validation.properties file. Location is:
src\main\webapp\WEB-INF\Validation\validation.properties
Content inside it is:
validate.user.yearsexperience = Only numbers can be used here.
validate.user.phone = Phone numbers can only be digits and should exactly be 10 digits.
validate.user.name.blank = Name cannot be left empty or Cannot contain more than 30 chars. It will accept letters only. No numbers or symbols are allowed either.
Then I setup following error codes in anticipation of bean validator catching one of these messages (but it did not).
typeMismatch.user.yearsexperience = Only numbers can be used here.
typeMismatch.yearsexperience = Only numbers can be used here.
typeMismatch.java.lang.Integer = Only numbers can be used here.
typeMismatch = Only numbers can be used here.
So when I enter wrong data for phone or name field, JSP shows those messages that I have set in my validation.properties file, but for yearsexperience field, it shows its own typeMismatch or cannot convert String to Integer, etc exceptions.
Here let me show you what Annotations I used over my model:
public class User{
#NotBlankNoFancyLettersOrNumbers(message = "{validate.user.name.blank}") //My own custom annotation. Works like a charm and when user enters wrong data, it shows my custom error.
private String name;
#Pattern(regexp="(^$|[0-9]{10})", message = "{validate.user.phone}") //javax's builtin validation constraint
private String phone;
#So many things I have tried. But first I started with #Pattern
private Integer yearsexperience;
For this Integer field, I cannot use #Pattern because it can only be put over String fields (am I correct here?).
Then I tried #Digits. When a user enters wrong input, it sure works, but doesn't show my error message, it shows a typeMismatch exception to the user. Then I made my own custom annotation and I gave it a name #DigitsOnly. Same issue, it would't show my error message. It shows the same old typeMismatch exception.
At this point, I thought maybe there is a problem with form binding (I should not have thought that because bean validation works properly for other fields). Anyway, I registered my own custom editor like this:
#InitBinder("user")
protected void binder(WebDataBinder binder, HttpSession session) throws Exception {
binder.registerCustomEditor(Date.class, new DateConvertor()); //This is unrelated to my question but it works!
binder.registerCustomEditor(Integer.class, "yearsexperience", new IntegerConvertor(session.getAttribute("user"), this.userdao));
}
This is my class that extends PropertyEditorSupport:
public class IntegerConvertor extends PropertyEditorSupport {
private User user;
private UserDAO userdao;
public IntegerConvertor(){}
public IntegerConvertor(Object object, UserDAO userdao) {
if (object instanceof User){
this.user= new User((User) object);
}
this.userdao = userdao;
}
#Override
public void setAsText(String value) {
if (!value.matches("[0-9]+")) { //if incoming value DOES NOT contains numbers only
setValue(this.userdao.getYearsExperienceBeforeChange(this.user.getId()));
} else {
setValue(value);
}
}
Now, I did this in a hope that typeMismatch exception will not be shown to the user, but still it shows that exception. (I knew that from this PropertyEditorSupport, or it wouldn't read custom error message from validation.properties file).
Anyway, then I tried to add a custom validator thru WebDataBinder.addValidator(myCustomValidator) like this:
#InitBinder("user")
protected void binder(WebDataBinder binder, HttpSession session) throws Exception {
binder.registerCustomEditor(Date.class, new DateConvertor());
binder.addValidators(new myCustormValidator());
}
This is how my custom validator looks,
public class MyCustormValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return User.class.equals(clazz); //return User.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
User user = (User) target;
String a = Integer.toString(user.getYearsExperience());
if(!a.matches("[0-9]+")){
errors.rejectValue("yearsexperience","validate.user.yearsexperience");
}
}
}
I converted that Integer to String because then I was able to check if the incoming value contains digits only and not alphabets. But here it causes another issue: it doesn't show the incoming value, it shows the old value. And because old value was already correct, it doesn't raise any issue. So I added else statement, just to throw an error in a hope that now at least it will show my custom error. But it does not!
Therefore, I have come here for help. Any help is greatly appreciated.
TL;DR Why custom error message doesn't show up for one field. But for other fields it works perfectly fine.
Why the custom error doesn't show up for some fields but for others it works perfectly fine
Because there are two different components are involved:
hibernate-validator that is using for bean validation
spring-mvc that is binding user values from a to a POJO class
You've properly configured message source for bean validation and it works.
But then you're customizing typeMismatch message and looks like Spring MVC doesn't see this. Typically it's enough to define bean with name messageSource but for some strange reason in your case you need something else.
My guess is that you have 2 contexts and messageSource is defined in the context that isn't visible to Spring MVC.
UPDATE:
I looked into example project on GitHub (http://github.com/farazdurrani/sscce) and I see that the problem with non-working typeMismatch message not because of the configuration. It happens because you're not using standard tags and try to show error messages manually (by invoking getDefaultMessage() method on each error from Errors.getAllErrors()). If you try to use <form:errors path="*"/>, you'll see that all error messages have been properly localized.
I have the following annotation
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = IdValidator.class)
public #interface Id {
String message() default "{Id field cannot be null}";
}
that I'm using on my class
public class Person {
#Id
String firstName;
#Id
String lastName;
}
When I call
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<FakePerson>> violations = validator.validate(person);
I want a ConstrainstViolation to have a message that says "firstName cannot be null". How can I do that?
Is there a way to create the messages associate with the path inside the ConstraintValidator?
public class IdValidator implements ConstraintValidator<Id, Object> {
#Override
public boolean isValid(Object value, ConstraintValidatorContext context)
{
//Ideally I can build the messages associate with the property here with ConstraintValidatorContext
}
}
There are a couple of options.
The first would be to add a new attribute to your annotation, shown below is label.
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PKFieldValidator.class)
public #interface Id {
String message() default "{Id field cannot be null}";
String label();
}
Then in the class, you'd use
#Id(label="firstName")
private String firstName;
#Id(label="lastName")
private String lastName;
Then simply make your message be something like
Id.message={label} is required.
But the problem with that approach is that you're dropping in bean field names into translations that could be in other languages. How much sense does Se requiere el firstName make in Spanish?
My preferred suggestion is to actually keep bean validation messages generic. Things such as Field is required or Must be greater than 0 seem very reasonable to me and allows for reuse.
For situations where you need to associate these violation messages with the properties, simply iterate the violations, get the propertyPath and print the path plus the message to your logs or whatnot.
From web frameworks, I prefer to do precisely that, iterate the constraint violations, adding them to a Map<String,String> where the key is the propertyPath and the value is the violation message. Then in the UI, I associate the message next to each of the field's input controls, keeping things clean and concise all the while easy for the user to understand and read.
Good evening, I'm trying to use Hibernate Validator, in the following scenario:
public class Car {
#NotNull
private String manufacturer;
#NotNull
#Size(min = 2, max = 14)
private String licensePlate;
#Min(2)
private int seatCount;
//setters and getters....
}
and I am trying to validate its attributes as follows:
public class CarMain {
public static Validator validator;
public static void main(String[] args) {
ValidatorFactory factory = Validation. buildDefaultValidatorFactory() ;
validator = factory. getValidator();
Car car = new Car(null,null,0);
Set<ConstraintViolation<Car>> st= validator.validate(car);
while(st.iterator.hasNext()){
ConstraintViolation<Car> cv = st.iterator.next();
System.out.println("Value: ("+cv.getInvalidValue()+") -->"+cv.getMessage());
System.out.println("Attribute: "+cv.getPropertyPath());
}
}
Here the whole entity is validated and the invalid values with the validation message and property path are displayed.
My question is:"Is it possible to validate only one attribute at a time with Hibernate Validator? Like I don't have to work with the whole object to validate it.
The Validator interface defines also a [Validator.validateProperty][1] method where you explicitly specify the property to validate. Mind you, you still need the object instance and you need to know the property you want to validate. This method is for example used by the integration of Bean Validation into JSF. Whether it makes sense to use it inm your case, will depend on your use case? Why don't you want to validate the whole object?
BTW, there is also Validator.validateValue which does not require an actual bean instance.
I would like to know what is the cleanest and best way to perform form validation of user inputs. I have seen some developers implement org.springframework.validation.Validator. A question about that: I saw it validates a class. Does the class have to be filled manually with the values from the user input, and then passed to the validator?
I am confused about the cleanest and best way to validate the user input. I know about the traditional method of using request.getParameter() and then manually checking for nulls, but I don't want to do all the validation in my Controller. Some good advice on this area will be greatly appreciated. I am not using Hibernate in this application.
With Spring MVC, there are 3 different ways to perform validation : using annotation, manually, or a mix of both. There is not a unique "cleanest and best way" to validate, but there is probably one that fits your project/problem/context better.
Let's have a User :
public class User {
private String name;
...
}
Method 1 : If you have Spring 3.x+ and simple validation to do, use javax.validation.constraints annotations (also known as JSR-303 annotations).
public class User {
#NotNull
private String name;
...
}
You will need a JSR-303 provider in your libraries, like Hibernate Validator who is the reference implementation (this library has nothing to do with databases and relational mapping, it just does validation :-).
Then in your controller you would have something like :
#RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, #Valid #ModelAttribute("user") User user, BindingResult result){
if (result.hasErrors()){
// do something
}
else {
// do something else
}
}
Notice the #Valid : if the user happens to have a null name, result.hasErrors() will be true.
Method 2 : If you have complex validation (like big business validation logic, conditional validation across multiple fields, etc.), or for some reason you cannot use method 1, use manual validation. It is a good practice to separate the controller’s code from the validation logic. Don't create your validation class(es) from scratch, Spring provides a handy org.springframework.validation.Validator interface (since Spring 2).
So let's say you have
public class User {
private String name;
private Integer birthYear;
private User responsibleUser;
...
}
and you want to do some "complex" validation like : if the user's age is under 18, responsibleUser must not be null and responsibleUser's age must be over 21.
You will do something like this
public class UserValidator implements Validator {
#Override
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
User user = (User) target;
if(user.getName() == null) {
errors.rejectValue("name", "your_error_code");
}
// do "complex" validation here
}
}
Then in your controller you would have :
#RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, #ModelAttribute("user") User user, BindingResult result){
UserValidator userValidator = new UserValidator();
userValidator.validate(user, result);
if (result.hasErrors()){
// do something
}
else {
// do something else
}
}
If there are validation errors, result.hasErrors() will be true.
Note : You can also set the validator in a #InitBinder method of the controller, with "binder.setValidator(...)" (in which case a mix use of method 1 and 2 would not be possible, because you replace the default validator). Or you could instantiate it in the default constructor of the controller. Or have a #Component/#Service UserValidator that you inject (#Autowired) in your controller : very useful, because most validators are singletons + unit test mocking becomes easier + your validator could call other Spring components.
Method 3 :
Why not using a combination of both methods? Validate the simple stuff, like the "name" attribute, with annotations (it is quick to do, concise and more readable). Keep the heavy validations for validators (when it would take hours to code custom complex validation annotations, or just when it is not possible to use annotations). I did this on a former project, it worked like a charm, quick & easy.
Warning : you must not mistake validation handling for exception handling. Read this post to know when to use them.
References :
A very interesting blog post about bean validation (Original link is dead)
Another good blog post about validation (Original link is dead)
Latest Spring documentation about validation
There are two ways to validate user input: annotations and by inheriting Spring's Validator class. For simple cases, the annotations are nice. If you need complex validations (like cross-field validation, eg. "verify email address" field), or if your model is validated in multiple places in your application with different rules, or if you don't have the ability to modify your model object by placing annotations on it, Spring's inheritance-based Validator is the way to go. I'll show examples of both.
The actual validation part is the same regardless of which type of validation you're using:
RequestMapping(value="fooPage", method = RequestMethod.POST)
public String processSubmit(#Valid #ModelAttribute("foo") Foo foo, BindingResult result, ModelMap m) {
if(result.hasErrors()) {
return "fooPage";
}
...
return "successPage";
}
If you are using annotations, your Foo class might look like:
public class Foo {
#NotNull
#Size(min = 1, max = 20)
private String name;
#NotNull
#Min(1)
#Max(110)
private Integer age;
// getters, setters
}
Annotations above are javax.validation.constraints annotations. You can also use Hibernate's
org.hibernate.validator.constraints, but it doesn't look like you are using Hibernate.
Alternatively, if you implement Spring's Validator, you would create a class as follows:
public class FooValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Foo.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Foo foo = (Foo) target;
if(foo.getName() == null) {
errors.rejectValue("name", "name[emptyMessage]");
}
else if(foo.getName().length() < 1 || foo.getName().length() > 20){
errors.rejectValue("name", "name[invalidLength]");
}
if(foo.getAge() == null) {
errors.rejectValue("age", "age[emptyMessage]");
}
else if(foo.getAge() < 1 || foo.getAge() > 110){
errors.rejectValue("age", "age[invalidAge]");
}
}
}
If using the above validator, you also have to bind the validator to the Spring controller (not necessary if using annotations):
#InitBinder("foo")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new FooValidator());
}
Also see Spring docs.
Hope that helps.
I would like to extend nice answer of Jerome Dalbert. I found very easy to write your own annotation validators in JSR-303 way. You are not limited to have "one field" validation. You can create your own annotation on type level and have complex validation (see examples below). I prefer this way because I don't need mix different types of validation (Spring and JSR-303) like Jerome do. Also this validators are "Spring aware" so you can use #Inject/#Autowire out of box.
Example of custom object validation:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { YourCustomObjectValidator.class })
public #interface YourCustomObjectValid {
String message() default "{YourCustomObjectValid.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class YourCustomObjectValidator implements ConstraintValidator<YourCustomObjectValid, YourCustomObject> {
#Override
public void initialize(YourCustomObjectValid constraintAnnotation) { }
#Override
public boolean isValid(YourCustomObject value, ConstraintValidatorContext context) {
// Validate your complex logic
// Mark field with error
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(someField).addConstraintViolation();
return true;
}
}
#YourCustomObjectValid
public YourCustomObject {
}
Example of generic fields equality:
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { FieldsEqualityValidator.class })
public #interface FieldsEquality {
String message() default "{FieldsEquality.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Name of the first field that will be compared.
*
* #return name
*/
String firstFieldName();
/**
* Name of the second field that will be compared.
*
* #return name
*/
String secondFieldName();
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
public #interface List {
FieldsEquality[] value();
}
}
import java.lang.reflect.Field;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
public class FieldsEqualityValidator implements ConstraintValidator<FieldsEquality, Object> {
private static final Logger log = LoggerFactory.getLogger(FieldsEqualityValidator.class);
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(FieldsEquality constraintAnnotation) {
firstFieldName = constraintAnnotation.firstFieldName();
secondFieldName = constraintAnnotation.secondFieldName();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null)
return true;
try {
Class<?> clazz = value.getClass();
Field firstField = ReflectionUtils.findField(clazz, firstFieldName);
firstField.setAccessible(true);
Object first = firstField.get(value);
Field secondField = ReflectionUtils.findField(clazz, secondFieldName);
secondField.setAccessible(true);
Object second = secondField.get(value);
if (first != null && second != null && !first.equals(second)) {
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(firstFieldName).addConstraintViolation();
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(someField).addConstraintViolation(secondFieldName);
return false;
}
} catch (Exception e) {
log.error("Cannot validate fileds equality in '" + value + "'!", e);
return false;
}
return true;
}
}
#FieldsEquality(firstFieldName = "password", secondFieldName = "confirmPassword")
public class NewUserForm {
private String password;
private String confirmPassword;
}
If you have same error handling logic for different method handlers, then you would end up with lots of handlers with following code pattern:
if (validation.hasErrors()) {
// do error handling
}
else {
// do the actual business logic
}
Suppose you're creating RESTful services and want to return 400 Bad Request along with error messages for every validation error case. Then, the error handling part would be same for every single REST endpoint that requires validation. Repeating that very same logic in every single handler is not so DRYish!
One way to solve this problem is to drop the immediate BindingResult after each To-Be-Validated bean. Now, your handler would be like this:
#RequestMapping(...)
public Something doStuff(#Valid Somebean bean) {
// do the actual business logic
// Just the else part!
}
This way, if the bound bean was not valid, a MethodArgumentNotValidException will be thrown by Spring. You can define a ControllerAdvice that handles this exception with that same error handling logic:
#ControllerAdvice
public class ErrorHandlingControllerAdvice {
#ExceptionHandler(MethodArgumentNotValidException.class)
public SomeErrorBean handleValidationError(MethodArgumentNotValidException ex) {
// do error handling
// Just the if part!
}
}
You still can examine the underlying BindingResult using getBindingResult method of MethodArgumentNotValidException.
Find complete example of Spring Mvc Validation
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.technicalkeeda.bean.Login;
public class LoginValidator implements Validator {
public boolean supports(Class aClass) {
return Login.class.equals(aClass);
}
public void validate(Object obj, Errors errors) {
Login login = (Login) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
"username.required", "Required field");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userPassword",
"userpassword.required", "Required field");
}
}
public class LoginController extends SimpleFormController {
private LoginService loginService;
public LoginController() {
setCommandClass(Login.class);
setCommandName("login");
}
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
#Override
protected ModelAndView onSubmit(Object command) throws Exception {
Login login = (Login) command;
loginService.add(login);
return new ModelAndView("loginsucess", "login", login);
}
}
Put this bean in your configuration class.
#Bean
public Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
and then You can use
<T> BindingResult validate(T t) {
DataBinder binder = new DataBinder(t);
binder.setValidator(validator);
binder.validate();
return binder.getBindingResult();
}
for validating a bean manually. Then You will get all result in BindingResult and you can retrieve from there.
Validation groups
Also it is worth to mention validation for some more complex cases, when you have some "multi steps" within your business logic. In such cases we need "validation groups".
#Validated annotation was added to support "validation groups" in validated bean. This can be used in multi step forms where in the first step you need, for example, validate name and email, and in the second step you need to validate, for example, phone number.
With #Validated you first need to declare groups. Groups are declared with your custom marker interfaces.
#Validated example
Let's say we have a scenario when we have a form for user sign up. On this form we want user to provide a name and email. And after user is signed up we have another form where we suggest the user to add his some extra information, for example, email. We don't want email be provided on the first step. But it is required to provide it on the second step.
For this case, we'll declare two groups. First group would be OnCreate, and the second group would be OnUpdate :
OnCreate:
public interface OnCreate {}
OnUpdate:
public interface OnUpdate {}
Our user UserAccount class:
public class UserAccount {
// we will return this field after User is created
// and we want this field to be provided only on update
// so we can determine which user needs to be updated
#NotBlank(groups = OnUpdate.class)
private String id;
#NotBlank(groups = OnCreate.class)
private String name;
#NotBlank(groups = OnCreate.class)
private String email;
#NotBlank(groups = OnUpdate.class)
private String phone;
// standard constructors / setters / getters / toString
}
We mark the validation annotations with our groups interfaces depending on which group those validations are supposed to be related.
And finally our Controller methods:
#PostMapping(value = "/create")
public UserAccount createAccount(#Validated(OnCreate.class) #RequestBody UserAccount userAccount) {
...
}
#PatchMapping(value = "/update")
public UserAccount updateAccount(#Validated(OnUpdate.class) #RequestBody UserAccount userAccount) {
...
}
Here we specify #Validated(...) instead of #Valid and specify the validation group which should be used in different cases.
Now depending on validation group we'll perform the validations for the particular fields within different steps.
I am looking at using Hibernate Validator for a requirement of mine. I want to validate a JavaBean where properties may have multiple validation checks. For example:
class MyValidationBean
{
#NotNull
#Length( min = 5, max = 10 )
private String myProperty;
}
But if this property fails validation I want a specific error code to be associated with the ConstraintViolation, regardless of whether it failed because of #Required or #Length, although I would like to preserve the error message.
class MyValidationBean
{
#NotNull
#Length( min = 5, max = 10 )
#ErrorCode( "1234" )
private String myProperty;
}
Something like the above would be good but it doesn't have to be structured exactly like that. I can't see a way to do this with Hibernate Validator. Is it possible?
You could create a custom annotation to get the behaviour you are looking for and then on validating and using refelection you could extract the value of the annotation. Something like the following:
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ErrorCode {
String value();
}
In your bean:
#NotNull
#Length( min = 5, max = 10 )
#ErrorCode("1234")
public String myProperty;
On validating your bean:
Set<ConstraintViolation<MyValidationBean>> constraintViolations = validator.validate(myValidationBean);
for (ConstraintViolation<MyValidationBean>cv: constraintViolations) {
ErrorCode errorCode = cv.getRootBeanClass().getField(cv.getPropertyPath().toString()).getAnnotation(ErrorCode.class);
System.out.println("ErrorCode:" + errorCode.value());
}
Having said that I probably would question the requirements for wanting error codes for these types of messages.
From the section 4.2. ConstraintViolation of the specification:
The getMessageTemplate method returns the non-interpolated error message (usually the message attribute on the constraint declaration). Frameworks can use this as an error code key.
I think this is your best option.
What I would try to do is isolate this behavior on the DAO Layer of the application.
Using your example we would have:
public class MyValidationBeanDAO {
public void persist(MyValidationBean element) throws DAOException{
Set<ConstraintViolation> constraintViolations = validator.validate(element);
if(!constraintViolations.isEmpty()){
throw new DAOException("1234", contraintViolations);
}
// it's ok, just persist it
session.saveOrUpdate(element);
}
}
And the following exception class:
public class DAOException extends Exception {
private final String errorCode;
private final Set<ConstraintViolation> constraintViolations;
public DAOException(String errorCode, Set<ConstraintViolation> constraintViolations){
super(String.format("Errorcode %s", errorCode));
this.errorCode = errorCode;
this.constraintViolations = constraintViolations;
}
// getters for properties here
}
You could add some annotation information based on what property has not validated from here, but always doing this on the DAO method.
I hope this helped.