According to the Java Community Process (JCP), Java Specification Request (JSR) 303 defines “…a meta-data model and API for JavaBean validation based on annotations…” Simply put, JSR 303 specifies a set of predefined annotations and a facility to create custom annotations that make it easier for Java Bean designers to define how their model objects will be validated at run time. Similar to and often used with JPA annotations, Javax Bean Validation annotations provide an easy way to apply common validation patterns driven by annotated constraints. These constraint annotations can specify default messages for constraint violations, or the messages can be resolved at runtime using the Java properties file facility.
In general, Java developers regularly apply design patterns when building applications or components. During the last decade we have embraced annotated programming as a means of applying common patterns, and Javax Bean Validation annotations follow common patterns that reduce the variability in model validation. With Javax Bean validation developers can validate the entire model with defined constraints at one time, or they can choose to validate specific constraints, based on model property or constraint grouping. Listing 1 is a snippet from an example Customer bean with both JPA and Javax Bean Validation annotations.
Listing 1
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
@Entity(name = "CUSTOMERS")
public class Customer extends AbstractModel {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private long id;
@NotNull(message = "{Customer.firstName.NotNull}")
@Size(min = 1, max = 30, message = "{Customer.firstName.Size}")
@Column(name = "FIRST_NAME")
private String firstName;
@NotNull(message = "{Customer.lastName.NotNull}")
@Size(min = 1, max = 30, message = "{Customer.lastName.Size}")
@Column(name = "LAST_NAME")
private String lastName;
@NotNull(message = "{Customer.email.NotNull}")
@Column(name = "EMAIL")
@Pattern(regexp = "^[\\w-]+(\\.[\\w-]+)*@([a-z0-9-]+(\\.[a-z0-9-]+)*?\\.[a-z]{2,6}|(\\d{1,3}\\.){3}\\d{1,3})(:\\d{4})?$", message = "{Customer.email.Pattern}")
private String email;
@NotNull(message = "{Customer.phone.NotNull}")
@Column(name = "PHONE_NUMBER")
@Pattern(regexp = "^\\(?(\\d{3})\\)?[- ]?(\\d{3})[- ]?(\\d{4})$|^(\\d{3})[\\.](\\d{3})[\\.](\\d{4})$", message = "{Customer.phone.Pattern}")
private String phone;
@NotNull(message = "{Customer.lastActivityDate.NotNull}")
@Future(message = "{Customer.lastActivityDate.Future}")
@Column(name = "ACTIVITY_DATE")
private Date lastActivityDate;
...
In Listing 1, the validation annotations are easy to understand. The firstsName and lastName fields have constraints on them imposed by annotations that do not allow null values and that constrain values to a certain size. The interesting thing about the @NotNull and @Size annotations is that they retrieve their respective constraint violation messages using a message key, {Customer.firstName.Size}, instead of defining a literal message string. The message arguments of Javax Bean Validation annotations can take a literal string as a default constraint violation message, or a key that points to a property value in a Java properties file that is in the root classpath of the application. Externalizing these string messages aligns the JSR 303 approach with other annotated programming and message processing techniques used by Java developers today.
The additional constraint annotation seen in the example in Listing 1 is @Pattern. This annotation, shown in its simplest form, takes both message and regexp arguments. The regexp argument is a Java string regular expression pattern that is applied as a matching constraint. In my testing, I tried supplying this value via String Enum arguments and by looking it up from a properties file, much the same way messages are resolved. This would not work as I kept getting the error, “The value for annotation attribute Pattern.regexp must be a constant expression.” However, I was able to use a static String constant. This seems to violate the convention of externalizing Strings to properties files; perhaps this will change in the near future.
Beyond size, null, and pattern constraints, JSR 303 has several other predefined constraint annotations that are listed in Figure 1. One need only consult the API documentation to discover their usage.
Figure 1
Reference Implementations
Like many of the later JSR initiatives, the reference implementation (RI) for JSR 303 is done by non-Oracle, open source development. In particular, JSR 303 is led by RedHat and the RI is based on Hibernate’s Validator 4.x. A second implementation that also passed the Technology Compatibility Kit (TCK) tests is provided by the Apache Software Foundation. In short this means that along with the Java Bean Validation API JAR from Oracle, one must also use one of these other implementations. For this example I chose the RI from Hibernate. Figure 2 shows the libraries, highlighted in yellow, required to use Javax Bean Validation. Additional logging libraries are required by the Hibernate implementation.
Figure 2
With the Hibernate Validator implementation, there are several additional constraint annotations provided, see Figure 3. You may notice the @Email and @URL annotations that provide constraints for well-formed email addresses and URLs, respectively. Of course these are not part of the RI and are considered extensions, albeit very handy extensions. Listing 2 is an example of what the email field would look like annotated by the @Email annotation.
Figure 3
Listing 2
@NotNull(message = "{Customer.email.NotNull}")
@Email(message = "{Customer.email.Email}")@Column(name = "EMAIL")
private String email;
Spring Web Flow Validation
The makers of Spring and Spring Web Flow have recognized the need for uniform Bean (model) Validation and have provided the ability to integrate JSR 303 Bean Validation into their containers. This integration combines the best of industry standard Spring Web Flow with the functionality of JSR 303. According to Spring Source, “Model validation is driven by constraints specified against a model object.” For this validation Spring Web Flow embraces two methodologies for validation: JSR 303 and Validation by Convention.
One of the issues with these two validation techniques is that they are not mutually exclusive. If JSR 303 validation is enable along with Spring’s Validation by Convention, duplicate error messages could be the result. The approach that I recommend is to use validation by convention and setup the validation methods to call JSR 303 validation on model objects as needed.
Validation by Convention using JSR 303
Validation by Convention makes it simple to map validation to Spring Web Flows. Listing 3 is a snippet from a Spring Web Flow definition, defining the enterCustomerDetails view-state to which the Customer model object is bound.
Listing 3
<view-state id="enterCustomerDetails" model="customer">
<binder>
<binding property="firstName" />
<binding property="lastName" />
<binding property="email" />
<binding property="phone" />
<binding property="lastActivityDate" />
</binder>
<transition on="proceed" to="reviewCustomerData" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>
Using Validation by Convention we follow the Spring Web Flow pattern of ${Model}Validator and create the CustomerValidator class to handle validation calls from Spring Web Flow. Inside this class, we must write methods that match the pattern validate${view-state} to link the validation routines to the corresponding Web flow view-state. Listing 4 is a an example of the CustomerValidator with the validateEnterCustomerDetails() validation method.
Listing 4
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.validation.ValidationContext;
import org.springframework.stereotype.Component; @Component
public class CustomerValidator {
public void validateEnterCustomerDetails(Customer customer,
ValidationContext context) {
Map<String, List<String>> propertyMap = new LinkedHashMap<String, List<String>>(); propertyMap.put("firstName", null);
boolean valid = ModelValidator.validateModelProperties(customer,
propertyMap);
if (!valid) {
MessageContext messages = context.getMessageContext();
for (Entry<String, List<String>> entry : propertyMap.entrySet()) {
String key = entry.getKey();
List<String> values = entry.getValue();
if (null != key && !key.isEmpty() && null != values
&& null != values.get(0) && !values.get(0).isEmpty()) {
messages.addMessage(new MessageBuilder().error()
.source(key).defaultText(values.get(0)).build());
}
}
}
}
}In Listing 4, the validateEnterCustomerDetails() method is called by Spring Web Flow when a view-state transition occurs. This method in turns calls the custom class/method ModelValidator.validateModelProperties() method and passes the model object and a map of bean properties to be validated in the model object. This technique allows us to use the provided conventions of Spring Web Flow with the annotated constraints of JSR 303 Javax Bean Validation. This is using JSR 303 manually.
Listing 5, is the source for the ModelValidator class that does the heavy lifting and works with the bean validation for each model bean. The idea here is that each Validation by Convention method, matching a Web Flow view-state, would make a call to the ModelValidator, passing in the desired properties to validate.
Listing 5
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class ModelValidator {
private static ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private static Validator validator = factory.getValidator();
public static boolean validateModelProperties(AbstractModel model, Map<String, List<String>> messages) {
boolean isValid = true;
if (null == messages) {
messages = new LinkedHashMap<String, List<String>>();
}
Set<ConstraintViolation<AbstractModel>> constraintViolations = null;
for (String key : messages.keySet()) {
constraintViolations = validator.validateProperty(model, key);
if (constraintViolations.size() > 0) {
isValid = false;
List<String> values = new ArrayList<String>();
for (ConstraintViolation<AbstractModel> violation : constraintViolations) {
values.add(violation.getMessage());
}
messages.put(key, values);
}
}
return isValid;
}
}
JSR 303 Custom Constraints
Along with the built-in constraint annotations provided by JSR 303 and Hibernate Validator, JSR 303 provides the facility to write your own custom constraints. Custom constraints are needed when you want to apply additional logic to your model validation. This logic would not be possible in the supplied constraints. For example, if you had a Reservation model object and you needed to validate the check-in and check-out dates, applying the logic that the check-out date should always be after the check-in date, you would need to write a custom constraint. With the supplied annotations, you can add constraints to force the dates to not be null and to be in the future (compared to today's date), but there is no supplied annotation that would perform the logic necessary for date comparison.
The thing to keep in mind is that creating custom constraints is a 2 step process. These steps can be done in any order, but we will start with creating the constraint annotation first. Listing 6 is an example of a custom constraint annotation. This annotation is applied to the model class, similarly to the @Entity JPA annotation. What should be noticed here is the validatedBy argument; its purpose is to link the constraint annotation to the implementation class, in this case ReservationDateRangeImpl. In other words, ReservationDateRangeImpl will perform the actual validation.
Listing 6
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Constraint(validatedBy = ReservationDateRangeImpl.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ReservationDateRange {
String message() default "The check-out date must be after the check-in date.";
Class[] groups() default {};
Class[] payload() default {};
}
Listing 7 is the implementation class ReservationDateRangeImpl. So when the model is validated, @ReservationDateRange will also be applied as a constraint.
Listing 7
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ReservationDateRangeImpl implements
ConstraintValidator<ReservationDateRange, Reservation> {
public void initialize(reservationDateRange reservationDateRange) {
}
public boolean isValid(Reservation reservation,
ConstraintValidatorContext context) {
if ((reservation.getCheckInDate() != null)
&& (reservation.getCheckOutDate() != null)
&& reservation.getCheckOutDate().before(
reservation.getCheckInDate())) {
return false;
}
return true;
}
}
A Word About Groups
Looking back at Listing 6, you might notice the line that refers to a Class[] array called groups. Annotations can be organized into groups; moreover, it is possible for each annotation to belong to several groups, including the DEFAULT group. With groups, you can specify what combination of constraints (supplied or custom) that you wish to apply during manual validation. The detractor here is that each group is represented by a Java Class type. To date, I have used interfaces as my group class types.
Issues with Custom Constraints - Separation of Concerns or Anemic Domain
Before you go off and write your own custom constraint annotations, take a moment to reflect upon the purpose and intended behavior of your domain or model objects. Albeit easy to do, writing custom constraint annotations to enforce business logic may not be the right call. Adding business logic to domain or model objects is generally considered to be a bad idea since it violates the paradigm of "Separation of Concerns" and mixes the business logic layers of an application with the domain or model layer of the application. I guess this really depends on what side you agree with in the "Anemic Domain" anti-pattern debate.
I have my beliefs, but I am not advocating in either direction. One could argue that the Reservation object is incomplete if we cannot constrain the behavior of the check-in/out date fields. It could also be argued that we have already violated the tenets of proper design by combining persistence in our domain objects, by adding JPA annotations to them. I look at it from a pure OOP perspective which combines data and behavior in each object. From there, I apply the "what makes the best sense" paradigm when designing my applications and using these techniques.
ICF Ironworks is always on the lookout for experienced professionals who believe in hard work, having fun, and great client service.
Thanks for the post. So I have to create a ModelValidator and another Validator for my object I want validated? Webflow won't do this validation automatically like MVC does?
Posted by: billy | 08/05/2011 at 06:08 PM
The ModelValidator is simply a class that I used to aggregate calls. You don't need it. You don't need JSR-303 Javax Bean Validation either with SWF. You could go this route: http://static.springsource.org/spring-webflow/docs/2.0.x/reference/html/ch04s10.html
Or you can use JSR-303 like so: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#validation-beanvalidation
Or this: http://static.springsource.org/spring/docs/3.0.0.RC3/spring-framework-reference/html/ch05s07.html
My approach lets you use JSR-303 constraint annotations, but also allows you to pick and use, based on view-state of your web flow, what constraints to validate.
Posted by: Jimmy Ray | 08/05/2011 at 08:52 PM
Do I need to have my model objects extend AbstractModel? When trying your example I get an error saying my model object is not type AbstractModel.
Posted by: billy | 08/09/2011 at 11:33 AM
yes, I have change the Customer type definition to read: public class Customer extends AbstractModel {
I do not supply this AbstractModel class, but you don't need my implementation to make this work. Worst case scenario, you could change generic type parameters in the ModelValidator.
Posted by: Jimmy Ray | 08/09/2011 at 12:54 PM
Yea thats what I ended up doing using a generic type. You've been very helpful but I have one more question. When stepping through the code I don't see how the valid property is ever false. In the ModelValidator it is set to true and then it is set to false inside the for loop of messages. messages is passed to the validateModelProperties method but in the CustomerValidator it is set as a new LinkedHashMap. What am I missing? It doesn't seem like the map will every have any records.
Posted by: billy | 08/09/2011 at 01:03 PM
The way we use it is that we pass model objects (Customer in this case) into the ModelValidator. The ModelValidator calls the JSR303 validator with this line: constraintViolations = validator.validateProperty(model, key);
If the validation fails: if (constraintViolations.size() > 0) {
isValid = false;
Then the isValid is marked to be true. If you create a Customer model instance and leave firstName null, you should see it return errors.
Posted by: Jimmy Ray | 08/09/2011 at 01:19 PM
Yea I understand that. The question was about the line above that. The for(String key : messages.keySet()). I just don't see how messages gets populated.
Posted by: billy | 08/09/2011 at 01:52 PM
messages is a map that is passed into the method. The messages map contains keys that are the strings ("firstName...") that match the bean properties that you are trying to validate. For each bean property, the ModelValidator calls validateProperty.
Posted by: Jimmy Ray | 08/09/2011 at 02:10 PM
Thanks again for the response. How does the messages map get populated in the CutomerValidator class? I see it declared like this Map> propertyMap = new LinkedHashMap>();
and then passed into the method.
Posted by: billy | 08/09/2011 at 02:25 PM
Put this (propertyMap.put("firstName", null);) in the CustomerValidator, before you call the ModelValidator.
Posted by: Jimmy Ray | 08/09/2011 at 02:38 PM
Got it! Thank you so much for helping me with this.
Posted by: billy | 08/09/2011 at 04:21 PM
I've been playing anourd with groovy/grails and really like the way you can fall back to standard java if needed. Looking at your list I feel ancient, but I've started exploring HTML5. Of all the things on the list I think Object-Oriented to the hybrid is most likely to happen because in my experience very few developers really understand OOP and they been writing bloated inefficient code that hopefully a hybrid solution may reign in. Speaking of trends what about process wise? I've been forced to look at Lean Software Development and it seems just another use for SCRUM and Agile techniques. Where do you think the methodology trends are going to take us?[]Reply from :I'm not sure where the methodology trends are going, but I know what I'd like to see. First, I want waterfall and big design up front to go away. Those just haven't worked on any teams or any situations I've been a part of. Second, I want agile stuff be a little less rigid. It's funny, but the people who are the biggest agile evangelists are generally the first people to tell you that you're doing it wrong. I'm not excited about using scrum, evo, xp, or whatever the flavor of the day is. All I want is something that works for my team regardless of whether I follow all the rules or not. I think it revolves anourd a few ideas though:1) Work in iterations2) Customer feedback at each iteration3) Test driven development (Unit and integration tests)4) Continuous integrationThat would be a pretty good start for most teams. Each of those seem pretty simple, but I'd be hard pressed to find evidence of any of these at most shops I've been at.[]
Posted by: Sandile | 06/17/2012 at 05:44 AM
A little silipistmc Cedric but some interesting concepts.the first problem is that@Author(date = February 26th, 2005 )will generate a compiler error because the element lastName is not specified and it does not have a default clause in the definition of @Author, so you can't actually omit it.If you solve that problem by specifying defaults in the declaration of Author, then you might still be stuck because you might not be able to determine whether an element (like lastName in your last example) was ommitted, or was actually explicitly set to its default value. It all depends on the API you are using. With com.sun.mirror and com.sun.javadoc you can make the distinction, with java.lang.reflect you cannot.To solve that you could look up the default value, and if the annotation's value was the default you could ASSUME it was unspecified (and therefore should inherit the value declared in super), but that assumption might be wrong.You can make the assumption less likely to be wrong by making the default value something ridiculously meaningless, or something that is actually semantically invalid (to your application not to the compiler). All you have to do in the processing is to convert the declared default value, to the semantic default value when processing, (or cry foul if a value must be specified somewhere, that is if at the application level there is no default, even though at the implementation level there is).You could possibly write a general purpose method to do partial inheritance processing. Element declarations in an annotation type can themselves be annotated, so you could tell the processing algorithm what the application level default value was with an annotation./**An annotation for annotation type members, specifying how the Partial Inheritance processor should process annotations of the target's enclosing annotation type.*/@Target(METHOD)@interface PartialInheritance {/**set to true if the processor needs to generate an error if no value for the target is specified in any of the declared or inherited annotations.*/boolean noRealDefault() default false;/**The target element's declared default value is used to detect whether the element value is being over-ridden or not and should not be a meaningful value. If a value is not specified in any of the declared or inherited annotations, then the value of this element, not the target's default will be taken as the value by the Partial Inheritance mechanism.*/String realDefaultString() default ;}@interface Author {/**The last name, if not specified, the value is inherited from superclass, if no inherited value is available either, the lastName is considered to be */@PartialInheritance(realDefaultString= ")public String lastName() default ~@~ ;/**The date. if not specified, the value must be inherited from a superclass's @Author.*/@PartialInheritance(noRealDefault=true)public String date() default ~@~ ;}
Posted by: Kawd | 06/17/2012 at 01:16 PM
I'm not sure I agree with mac. Metadata may be a non object ornetied concept, but in Java annotations are attached to object ornetied constructs. They belong to the class (or feature) they are defined in. Fields and methods also belong to the class they are defined in and they can be inherited. Why shouldn't annotations be inherited according to the same rules that are already well understood by Java developers? That is one of the first things that occured to me when I was first learning about Java annotations, so I can attach one of these to one of my Classes and read it back at runtime, cool but it won't get inherited, hmmm.. And it doesn't seem that confusing to me as far as using annotations that can be inherited. The only confusing part that I can see would be writing tool or framework that has to correctly infer inheritance where none really' exists. I would have rather seen it as part of the official release because I'm not sure that widespread use of both inheritable and standard annotations would be a good thing. For instance what if a framework I use enables inheriting annotations that it processes. If I add my own annotations that the framwork knows nothing about then I have classes with both types in it. That sounds like trouble to me.But I don't buy the it's not OO, leave it alone argument: a big part of OO is rolling different language features into a Class and handling them in a consistent way. And as far as `Applying Inheritance or Partial Inheritance mecanism` : thats how everything else is done in Java so why not just phrase it `Applying the standard Java inheritance mecanism`
Posted by: Stephanie | 06/17/2012 at 01:23 PM
Actually Annotations do support Inheritance of a kind.That kind thuogh is that an annotation can be declared (using the meta-annotation @Inherited) to be inherited from classes to subclasses ONLY. Unfortunately (IMHO) many of the useful applications of inheritance would be inheriting from interfaces and JSR-175 s inheritance mechanism does not allow this.Also the inheritance is a full inheritance, not the partial inheritance that Cedric discusses, and this, combined with the fact that we cannot extend an annotation type (therefore making our own similar annotation, being a kind of the other) and define different default values, makes annotations considerably less useful than they might otherwise be.Given these limitations in JSR-175 The concept of partial inheritance has proven useful to Cedric, and I can understand why. Since my comment yesterday I have written a meta-annotation for member elements, and a java.lang.reflect processing engine that can (for the examples above) return an Author object that exhibits the sort of partial inheritance Cedric discussed. This engine is general purpose and not restricted to one particular annotation type.
Posted by: James | 08/28/2012 at 06:41 AM