Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Using Annotations to add Validity Constraints to JavaBeans Properties

 
By Anders Holmgren, March 2005  

Articles Index

Currently there is no standard way to add validity constraints to Java classes. This is in contrast to other languages like XML in which (using XML Schema) relatively rich constraints may be specified. With the advent of annotations (JSR 175) in the Java 2 Platform, Standard Edition (J2SE) 5.0 a convenient mechanism now exists to allow such constraints to be specified in the Java meta model. This article explores how annotations might be used for this purpose and discusses why this may be an important capability.

This article is made up of 3 parts:

  • Part 1 gives an introduction to the problem that we are trying to solve.
  • Part 2 covers our approach to solving this problem by leveraging J2SE 5.0 annotations.
  • Part 3 contains further discussion on the topic.

Part 1: A Brief Look at Constraints

In this section we'll take a look at what Java has to offer in terms of support for constraints and compare this with other technologies that a Java 2 Platform, Enterprise Edition (J2EE) developer would typically encounter. First, lets start with some definitions to make sure we are all on the same page.

What we mean by the term constraint (for the purposes of this article) is simple field level constraints on data. For example, to store a person's age we may define an integer field called age. In plain English, a constraint on this field would be something like "the age must be greater than or equal to 0".

When we define an entity, such as a person, in a language like Java then we do so in the meta model of that language. In the case of Java, the meta model that we use is for this is a Class. So if we define an age field as "private int age;" then we are specifying three pieces of meta information: the visibility; the type and the name. The meta model for Java is defined in the Java Language Specification.

Why We Care About Constraints

In most computer systems, the integrity of the data is paramount. For example, the data may represent a bank account or patient record. The usefulness of that information, to the organization that maintains it, is typically related to the accuracy and integrity of that information. For example, if a bank account does not have a valid account number then it is difficult for the bank to make proper use of that account.

In an n-tier system, the business tier exposes services that provide an access point to parts of a business process that is being automated. One of the key responsibilities of these business services is to ensure that the integrity of the business data is maintained. They do this by enforcing certain business rules on that data during the life cycle of that data. Additionally, databases are normally designed with a certain degree of integrity checking built in.

Unfortunately, if no integrity checking is performed on the data prior to it reaching these business services, then the user experience may suffer because of the time it takes between the user submitting the information and the user being informed of errors in the data. Additionally, increased load is placed on the system as it needs to cope with a higher amount of messages to the business services for each business transaction.

To alleviate these problems, it is common to do some integrity checks on the data closer to the end user. Some checks may be done in the browser using the facilities in HTML forms or by using JavaScript. Checks are also typically done in the presentation tier which is the front door to the server side processing. These checks are a subset of the overall business rules as it is typically impractical to attempt to validate the data completely. A typical example would be for a credit card number. You might check that the card number adheres to the correct format in the browser or presentation tier, but ensuring that it is a valid credit card number and has sufficient credit will typically be left to the business tier.

In trying to solve this problem, however, new problems are often introduced. One problem is that the business rules are duplicated. For example if you have a rule specifying the constraints on a credit card number and you hand code this check in the browser, presentation tier and business tier, then you have duplicated that rule three times. If the rule changes then you need to change this in each place. This is clearly a maintenance issue.

Another problem is missing rules in the business tier, as a result of adding those rules in the browser or presentation tiers. This may occur deliberately to avoid maintenance problems associated with duplication, or accidentally because the data reaching the business services no longer has these integrity issues.

At first this may not seem like a problem. However, it means that the business services are no longer in charge of maintaining the integrity of that data, but are instead trusting their clients to look after some aspects of that integrity. The moment a business service is reused, then the new clients must know that they need to do the same validity checks or you now risk the integrity of your businesses data.

In an SOA world, where we want to increase the reuse of business services, we cannot afford to have the business services not taking complete responsibility for the validity of the data. This is particularly important for business services that are used between separate organizations such as business partners. Clearly, you can't rely on your business partner to ensure the validity of your businesses data.

One approach to solving this issue (at least partially), is to declaratively define a set of constraints on the data, that can be shared by various producers and consumers of that data. This approach is adopted by the Web Services standards which use XML Schema to define data exchanged in these services.

This is also the approach adopted within this article, where we introduce a mechanism to define constraints using the new Annotation facility in J2SE 5.0. But first, we dig a little deeper into constraints.

Comparing Constraint Facilities in Some Meta Models

Consider Figure 1 below. It shows a simple application which captures address information from the user in a web browser, transfers it to a J2EE application server which in turn both stores the address in a local database as well as passes the address to a partner's web service. In addition, there is a simple business rule (constraint) associated with the address specifying that the street field cannot be greater than 20 characters in length.

Figure 1: Constraint Facilities of Some Meta Model
Figure 1: Constraint Facilities of Some Meta Model
Click to enlarge


At each node in the application, the address data is captured in a different format and governed by a different meta model. Taking the street field, we can see that in the browser it is in the form of an HTML text input field; in the application server it is in the form of a String property of an Address Java class; in the database it is a VARCHAR column of an ADDRESS table; and in the partner's web service it is a string element within an address element of an XML document.

Lets take a closer look at the meta model that applies to the address data at each of these nodes.

In The Browser

Here we represent the street field as an input text field. In HTML 4.0 the meta model of an input field allows for some constraints to be placed on the data to be captured. In particular it allows us to specify a maxlength attribute (as shown in the diagram) which will cause browsers to prevent the users from typing more than that many characters into the field. The meta model allows for the definition of this constraint and the browser will enforce the constraint.

Beyond maxlength there are some other constraints, such as option lists, which restrict the user to choose one of a fixed set of options, but on the whole the list of constraints is fairly limited. As a result, web developers typically resort to the use of JavaScript to enforce other constraints in the browser.

The next generation of forms known as XForms leverages the XML Schema meta model and contains a far richer set of constraints.

In The Database

In the database we represent the street field as a VARCHAR column of an ADDRESS table. The relational database meta model allows us to specify a maximum length constraint on VARCHAR columns as is shown in the diagram. So once again, the meta model allows for the definition of the constraint and the RDBMS will enforce the constraint.

The constraint mechanisms of modern DB's are pretty rich these days.

In The Web Service

Web Services leverage XML Schema as the meta model for the input and output data of WSDL operations. XML schema allows a relatively rich set of constraints to be specified. For example, our business rule of a maximum of 20 characters on the street field is no problem in XML schema.

The list of XML Schema constraints (known as simple type facets) are: length, minLength, maxLength, pattern, enumeration, whiteSpace, maxInclusive, maxExclusive, minInclusive, minExclusive, totalDigits and fractionDigits.

In The Application Server

In the application server the street field is represented as a String property of a Java class. The Java meta model does not have a standard mechanism to specify any constraints on a String field. So we cannot directly capture the business rule that restricts the street field to 20 characters, as part of the definition of the street field. Typically such constraints end up being enforced in code.

Others have discussed this issue before and proposed some potential solutions. We will cover some of these later in the article.

As you can see there is a fair difference in these four meta models when it comes to defining constraints. In the next section we discuss why we think that being able to specify constraints declaratively is a useful thing.

Why Meta Model Constraints are Useful

The problem is not that you can't enforce such constraints in the Java programming language. Clearly you can do so programatically as the following example shows.

public class Address {
  ...
  public void setStreet(String newStreet) throws ConstraintViolationException {
    if ((newStreet != null) && (newStreet.length() > 20))
      throw new ConstraintViolationException();
    street = newStreet;
  }
  ...
}

But how does this differ from, say, the XML example (reproduced below) where the constraint was captured (declaratively) in the meta model?

<complexType name="Address">
  ...
  <element name="street">
    ...
    <restriction base="string">
      <maxLength value="20"/>
  ...
</complexType>

First thing you might notice is that the enforcement is quite ad hoc. We could have written the Java code in different ways. In fact, it would be quite easy to have forgotten to do a null check first. The second thing is that (ignoring the verbosity of XML Schema) there is more code required to procedurally capture the constraint.

Another difference is that there is no separation of the definition of the constraint, from the enforcement of it. In fact, the Java version does not really define the constraint at all, except possibly informally in the JavaDoc. The XML version, in contrast, does not contain any enforcement code. The enforcement would be done in some other language like Java. However, a very important difference is that this code is likely to be part of a framework, rather than an application. Very little, if any, application code would be required for enforcement.

And the last difference is that there is no way to programatically determine what constraints are present on the street field. For example, we can't introspect on the field and find the constraints.

By separating the definition from the enforcement, and by providing a way to introspect these constraints, we gain much more flexibility in the enforcement. This is particularly useful when integrating with other frameworks and also for sharing with other meta models (which we will cover in the next section). It also allows us to potentially employ different enforcement strategies over time or in different circumstances.

Sharing Constraints Between Meta Models

Not only would it be nice to have a standard mechanism for specifying and enforcing constraints within the Java programming language, it would also be useful to be able to share constraints between Java and other meta models.

In Figure 1, we showed an example where the Address data moved between 4 different meta models. The diagram showed how the maximum length constraint on the street field could be defined in the various meta models (except Java).

Ideally we would like to define this constraint only once and then share it between each of these meta models. For example we would like to be able to do the following sorts of things.

  • Application Server <-> Browser. The code (a JSP tag) that generates the HTML input element for the street field, could look at the constraints associated with the street property in the Java Address class and automatically add the maxlength attribute to the input field.
  • Application Server <-> Web Service. If we use a tool to generate the proxy to talk to the web service, this tool could look at the constraints on the street element of the address and automatically generate a corresponding constraint in the Java class. We could also go in the other direction: if we generate a web service (from an EJB) we could add the constraints to the XML Schema definition that is included in the WSDL.
  • Application Server <-> Database. If we use a tool to automatically generate the database schema from the Java classes, then appropriate constraints could be automatically generated for the columns. This also works in the reverse direction, when generating Java classes from DB tables.

To be able to do all of the above, we need to be able to programatically access to the constraints.

Current State of Affairs for Constraint Checking in Java

Most applications today do constraint checking in a fairly informal and ad hoc manner. It is not uncommon to see constraint checking coded manually in the HTML forms, web applications, or EJB's, with little or no attempt to avoid code duplication. It is also not uncommon to find constraint checking missing from some of these places or indeed be inconsistent between them. There are likely many cases where the business rules changed but not all the checking code was changed, leading to unexpected behaviour.

Others have discussed this issue before and presented some solutions. The following is a summary of the different approaches we have come across. The resources section contains links to articles that discuss these approaches. Note these articles are worth reading in their own right.

  1. XML Schema Approach
    In this approach, the developer creates XML Schema definitions of entities (like address) and then generates Java classes from them. The constraints that are associated with the XML schema are then used for validation within the corresponding Java classes. So in effect this approach leverages an external language that supports constraints, to provide those missing capabilities within Java. JAXB is the standard for binding XML into Java.

    This is an effective approach due to the prominence of XML and the fact that XML Schema supports the most common kinds of constraints that developers want to use. The main downside is that it forces developers to code all the entities in XML, rather than Java, which complicates the development process. The next version of JAXB is going to add support for Java to XML bindings. However, the early draft (23 June 2004) does not appear to allow the specification of constraints within Java.

    Another downside with the current implementation (V1.0) of JAXB is that, while it provides validation of the constraints from Java, it does not expose the constraints as meta data from within Java. You need to directly process the XML Schema definition if you want access to the constraints.

  2. JavaBean's PropertyDescriptor's Approach
    This is a pure Java solution that adds constraint meta data to JavaBeans property descriptors. It leverages the JavaBeans BeanInfo mechanism for adding arbitrary meta data to JavaBeans classes. The following code snippet shows how the maximum length constraint could be added to the street properties meta data using this approach.

    public class AddressBeanInfo extends SimpleBeanInfo {
      public PropertyDescriptor[] getPropertyDescriptors() {
        try {
          PropertyDescriptor streetDesc = 
            new PropertyDescriptor("street", Address.class, "getStreet", "setStreet");
          streetDesc.setValue("MAX LENGTH CONSTRAINT", new Integer(20));
    
          return new PropertyDescriptor[] { streetDesc };
        } catch(IntrospectionException ex) {
          return null;
        }
      }
    } 
    


    This is another effective approach, that has the advantage of allowing the programmers to code the constraints in Java. In addition, because the constraints are added to the PropertyDescriptor's, the constraints are available as meta data from within Java.

    The main downside is that it requires a relatively large amount of code. Also the constraints are separated from the property definitions which, like deployment descriptors in J2EE, complicates the development process.

  3. Static Constraint Object Approach
    In this approach, static instances of constraint classes are added to the class definition of the entity. A maximum length constraint might be added to the street property as follows.

    public class Address {
      public static Constraint STREET_CONSTRAINT = new MaxLengthConstraint(20);
      private String street;
      public void setStreet(String street) throws ConstraintViolationException {
        STREET_CONSTRAINT.validate(street);
        this.street = street;
      }
    } 
    


    Note, while the example shows the constraint being validated in the setter method, this is just one implementation strategy and not essential for this approach.

    To add more than one constraint to a property, a composite constraint object is created and the individual constraints are added to that, as shown below.

    public class Address {
      public static Constraint STREET_CONSTRAINT;
      static {
        CompositeConstraint cc = new CompositeConstraint();
        cc.add(new MaxLengthConstraint(20)).add(new MinLengthConstraint(10));
        STREET_CONSTRAINT = cc;
      }
    } 
    


    Calling the validate method on the CompositeConstraint instance, will result in the validate methods of the contained MaxLengthConstraint and MinLengthConstraint instances to be called in turn. Another option is to create constraint classes that cover more than just one constraint type. For example, a numeric constraint could check for minimum and maximum values within the one constraint class.

    This is also a pure Java approach, and like the JavaBean's PropertyDescriptor's Approach, it does take a bit of coding. The main difference is that the constraints are not strictly part of the meta data for the property. Instead, this approach relies either on manual coding to validate data, or on a naming scheme to relate the property with its constraints.

    The above example shows such a naming scheme. Here the property called 'street' has a corresponding static constraint called 'STREET_CONSTRAINT', defined in the same class. If we adhere to this convention then we can use introspection to find the constraints that are associated with properties.

  4. Framework Specific Solutions
    Many frameworks such as Java Server Faces (JSF) and Struts provide validation facilities. For example, JSF allows Validators to be added to UI fields. This is similar in concept to adding constraints to JavaBeans properties, but the applicability is confined to the UI component. This does not provide a general purpose mechanism for adding constraints to JavaBeans properties and is not useful outside the scope of the framework.

This is by no means an exhaustive list of possible approaches to solving this problem. Many other approaches are possible including having separate classes for each constrained property (having a Street class for the street property instead of String). What we have covered are some of the more popular and successful approaches that we are aware of.

Key Features of An Ideal Solution

We have provided an overview of some of the existing approaches and pointed out where we believe there is room for improvement. In wrapping up the first part of this article, let us outline what we see as some of the more important features of an ideal solution. We believe an ideal solution would have the following properties.

  1. It would be pure Java.
  2. It would be simple and concise to use. Constraints would be added declaratively with a minimal amount of code and collocated with the property definitions.
  3. It would be easy for programs to get access to the constraints on properties.
  4. It would be generally applicable to any JavaBean and not limited in scope to specific frameworks.
  5. It would be standardized.
  6. It would come with a standard set of constraints while allowing for additional constraints to be defined.
  7. It would be integrated with various frameworks to allow for sharing of the constraints between different meta models. Particularly useful would be integration with JAXB, JSF and persistence frameworks like JDO.

In the remainder of this article we will discuss the use of J2SE 5.0 annotations for defining constraints. We believe this fulfils the first four goals and that it would be a good basis for a standard. The remaining goals are beyond the scope of this article.

Part 2: Annotating Constraints

Let's explore a possible way to add constraints by using annotations. If you are not familiar with annotations, then we recommend you read some other articles before you continue.

The code below shows a snippet of an Address class with a @MaxLength(20) annotation added to the street field. The intended meaning of this annotation is that the street field has a constraint associated with it specifying that the length cannot be greater than 20 characters.

public class Address {
    @MaxLength(20)
    public String street;

    ....
}

You may have noticed that the street field is declared public. I will deal with this later in the article, but let's ignore this for now.

We can define the MaxLength annotation as follows.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MaxLength {
    int value();
}

Note the annotation is defined by the use of the @interface keyword. The @Retention and @Target bits are annotations (or more formally meta-annotations) that apply to the MaxLength annotation (meta data of meta data). Here the @Retention annotation is set to RUNTIME which means the annotations can be introspected at runtime. The @Target annotation is used to say that the MaxLength annotation can only be applied to fields.

The following code snippet shows how you can use reflection to get the MaxLength constraint for the street field.

// introspect to get a list of annotations on the street field.
Field f = Address.class.getField("street");
Annotation[] annotations = f.getAnnotations();

// iterate over the annotations to locate the MaxLength constraint if it exists
for (Annotation a : annotations) {
    if (a instanceof MaxLength) {
        MaxLength ml = (MaxLength)a; // here it is !!!
    }
}

Now that you have the constraint you can validate values against it. The following code illustrates this.

MaxLength ml = ... // from above
String value = "101 first st";
if (value.length() > ml.value()) {
    // The constraint is violated
}

It's straightforward to add constraints to fields and validate them by using annotations and the runtime reflection capabilities associated with them. However, this example relied on the field being public, or else the introspection would have failed. This short coming will be addressed next.

JavaBeans Version

So let's adapt this approach to a JavaBeans model, and while we are at it, get a bit more sophisticated in the processing. In this case the field will be private and there will be associated public getters and setters. As mentioned, annotating the field is not useful as we can't sensibly introspect it when it's private.

Fortunately there are other places we can put the annotations where we can introspect them. The choices are as follows:

  • Getter method
  • Setter method
  • Setter method's parameter

Any of these are possible, and in fact all could be allowed, but we will concentrate on the setter method itself. To be easy for frameworks to identify constraints, it would be useful to have something to mark them out from other annotations. There is no inheritance for annotations, but luckily there is another mechanism -- annotations for annotations. Below is a simple marker meta-annotation that will be applied to our constraint annotations (like MaxLength).

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Constraint {}

The @Target(ElementType.ANNOTATION_TYPE) indicates that the Constraint annotation will be applied to other annotations (that it is a meta-annotation). Now let's use this to define a new constraint - MinLength.

@Constraint
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
public @interface MinLength {
    int value();
}

Note the @Constraint marker annotation is now included and the @Target specifies that the annotation is allowed on fields, methods and method parameters.

Now we are ready to define a JavaBeans version of Address with an additional MinLength constraint. This is shown below.

public class Address {
    private String street;

    public String getStreet() {
        return street;
    }

    @MinLength(10)
    @MaxLength(20)
    public void setStreet(String street) {
        this.street = street;
    }
}

Let's now start to put together some code to get at these property-based constraints. First, we write a simple method to determine if a given annotation is a constraint.

/**
  * Returns true if the annotation is a Constraint. I.e. Contains the Constraint marker.
  */
public static boolean isConstraint(Annotation annotation) {
     if (annotation == null)
         throw new IllegalArgumentException("null is not a legal value for annotation");
     return annotation.annotationType().isAnnotationPresent(Constraint.class);
}

Next we build on that to go through an array of annotations and return from that a set of constraints (Annotations that pass the isConstraint test above). This method will be used later.

/**
 * Returns the set of constraints that are contained within the array of annotations.
 * If no constraints found then the set will be empty.
 */
private Set<Annotation> getConstraints(Annotation[] annotations) {
    Set<Annotation> constraints = new HashSet<Annotation>();
    for (Annotation a : annotations) {
        if (isConstraint(a))
            constraints.add(a);
    }
    return constraints;
}

Finally we write a method to give us the set of constraints associated with a given property of a given class. Exception handling has been omitted for clarity.

public Set<Annotation> getConstraints(Class<?> theClass, String propName) {
    /*
     * Find the descriptor for the property name given and return the 
     * constraints from the write method.
     */
    PropertyDescriptor[] descs = 
        Introspector.getBeanInfo(theClass).getPropertyDescriptors();
    for (PropertyDescriptor desc : descs) {
	if (propName.equals(desc.getName()) {
            Method write = desc.getWriteMethod();

            // get the constraints off the write method. This calls the previous
            // method passing the array of annotations from the write method.
            if (write != null)
                return getConstraints(write.getAnnotations());
        }
    }

    // if we get here then we didn't find the property or its write method - exception
    ...
}

So we now have a simple way to annotate properties on JavaBeans and a simple method to get at them later.

To be perfectly honest, we've taken a few liberties with the above code in order to make it simpler to follow. In reality, you wouldn't want to create a new set of constraints from scratch each time. You'd really want to do it once and cache it. Also, the above assumes that the setter methods are not being overridden. In the real world, you'd want to cater for the setter method being inherited from a parent class and also having the setter method being overridden, one or more times, in the inheritance hierarchy. What we want, is the union of all the constraints on the setter method, such that all the constraints on the property, regardless of where they are defined, must be satisfied. This means walking up the inheritance hierarchy, looking for all the definitions of the setter method and collecting the corresponding constraints. You can download the complete code from the resources section.

More Constraints

Clearly minimum and maximum lengths are just two examples of constraints that could be added using Annotations. We could define annotations for many of the XML Schema constraints like @Pattern("(\\d{4}\\-){3}\\d{4}"), @MaxInclusive(56) etc. While there is no need to limit the possible constraints that could be defined, it would be valuable to have a standard set of constraints that you could always rely on to be there. We believe a good starting point is probably the simple type facets from XML Schema. Additionally, for collections, we could adopt the minOccurs and maxOccurs that are applied to elements in XML Schema. Click here to see examples of some more potential constraints.

Validation

Now we have an extensible mechanism for defining constraints on JavaBeans properties and a straightforward way to get at them at runtime. What we need next is a validation framework that allows us to check whether a given value is valid for a given property of a given class. The following code snippet gives you the general idea.

public ValidationResult validate(Class theClass, String propertyName, Object value) {
    Set<Annotation> constraints = getConstraints(theClass, propertyName);
    for (Annotation c : constraints) {
        // check for violations against the constraints
    }
    // return the result.
}

Note that this simple validation method is usable in several different ways. For example, you could call this inside the setter method if you wanted to prevent invalid values being set as shown in the following code snippet. The Validator is some instance of a class that implements the validate method shown above.

public void setStreet(String street) throw ValidationException {
    ValidatonResult result = theValidator.validate(getClass(), "street", street);
    if (// result shows street value not valid)
        throw new ValidationException(result);
    this.street = street;
}

Or if you prefer you could allow the value to be set and then validate the field afterwards.

String street = "123 highway 1 ....";
address.setStreet(street); // note setStreet() no longer throws exceptions
...
ValidatonResult result = theValidator.validate(address.getClass(), "street", address.getStreet());
if (// result shows street value not valid)
    // do something

Still another option is to validate a value externally from the bean and only call the setter if it is valid. The last option may be useful when integrating with a presentation framework like JSF.

String street = "123 highway 1 ....";
ValidatonResult result = theValidator.validate(Address.class, "street", street);
if (// result shows street value not valid)
    // do something
else
    address.setStreet(street);

We won't go into more details on validation for now. Hopefully we've shown enough to demonstrate that you could implement a sophisticated validation framework around these constraints and that you could even integrate it in to some existing validation framework. In this way, you could get a seamless framework that handled simple field constraints, as well as more complex cross field validations and so on.

A validation framework is included with the downloadable source.

IDE Support

One of the advantages of using annotations is that J2SE 5.0 aware IDEs may provide auto-completion for annotations. This makes it very easy for developers to see the list of constraints that can be applied. The following screenshot shows this feature in NetBeans 4.

Figure 2: Screenshot showing constraints in auto-completion list in NetBeans 4
Figure 2: Screenshot showing constraints in auto-completion list in NetBeans 4
Click to enlarge


It's also worth pointing out that this works best when we have each annotation applying a single constraint and the name of that annotation being fairly self explanatory. For example, it wouldn't be hard to figure out what adding a MaxLength annotation might do, once you were familiar with applying constraints this way. Aligning these annotation names with the names used for corresponding constraints in XML Schema, further simplifies things for developers familiar with XML Schema.

The end result is a strong development story around applying property constraints, without the need for custom IDE support (i.e. it's out of the box).

Part 3: Further Discussion

In this section we discuss some other possibilities as well as some limitations of this implementation.

Constraints as Part of a Property Annotation

We have covered the use of annotations for constraints in isolation from other annotations. The reality is that as developers move to J2SE 5.0, annotations will be created for many things. One area that seems likely, is a set of annotations for JavaBeans properties as a whole. In this case the property-based constraint annotations, as discussed in this article, would need to work together with these annotations or may even be folded into this set. As a simplistic illustration, you might have something like this.

public class Address {
    @Property @MinLength(10) @MaxLength(20) String street;
    ....
}

Note the @Property annotation is a slightly different type of annotation from constraint annotations, as it would need to be processed at build time in order to generate the getter and setter methods.

Build Time vs. Run Time

We based our approach on the use of the runtime support for annotations, because we wanted the constraints to become part of the class's meta data, in a similar way that constraints within XML Schema are part of the meta data for XML based data. Annotations, however, may also be accessed at build time.

Included with the J2SE 5.0 release is a tool known as the Annotation Processing Tool (APT), which is designed to support build time processing of annotations. It allows us to generate additional code, such as additional classes, based on annotations within existing classes.

This may be particularly useful, if you want to ensure that property validation is always done in the setter method. To illustrate this, let's revisit our Address class, which is reproduced below.

public class Address {
    private String street;
    public String getStreet() {
         return street;
     }
     @MinLength(10)
     @MaxLength(20)
     public void setStreet(String street) {
         this.street = street;
     }
}

Using APT we could generate a decorator class (applying the GOF decorator pattern) to enforce validation in the setter method as shown below.

public class AddressValidationDecorator extends Address {
    ...
    public AddressValidationDecorator(Address address) {
        this.address = address;
     }
     ...
    public void setStreet(String street) {
         ensurePropertyValid("street", street); // throws exception if invalid
         address.setStreet(street);
     }
...
}

Having validation in the setter method is simply a matter of constructing and using the decorator as shown below.

Address a; // from somewhere
Address a2 = new AddressValidationDecorator(a);
a2.setStreet("123 better make it valid lane"); // would get an exception if street invalid

Boolean Limitations

The approach we have outlined mirrors the approach taken by XML Schema, in that we have explicitly defined a set of constraint objects that can be applied to a property. For example, to constrain the street property to a maximum length of twenty characters, we declaratively add a MaxLength constraint object to the street property's meta data. Enforcing this constraint, is logically equivalent to enforcing the following boolean expression:

 street.length() <= 20

When there is more than one constraint applied to a property, all those constraints must be adhered to for the property to be valid. In other words, there is an implicit AND operation between the constraints. For example, if we define a minimum length constraint and a maximum length constraint on the street property as follows:

 @MinLength(10) @MaxLength(20) public void setStreet(String street)

then enforcement of these two constraints is logically equivalent to enforcement of the following boolean expression:

 (10 <= street.length()) AND (street.length() <= 20)

Note, XML Schema also has an implicit ANDing of constraints, as do frameworks like JSF. There is no support for the use of an OR operator between constraints, nor the use of a NOT operator, nor grouping operators to control operator precedence. In other words, there is only partial support for boolean expression.

We consider alignment with XML Schema more important than more complete boolean expression support. We also feel, as the designers of XML Schema and JSF presumably do, that this approach results in a good compromise between power and complexity. The end result is that, while it may not cover every situation, it is easy to use and covers many common cases.

Java-Style Boolean Expression Approach

An alternative approach is to have a single annotation to which we pass a boolean expression. For example, the setStreet method could be written as:

@Constraint("(10 <= street.length()) && (street.length() <= 20)") 
public void setStreet(String street)

As we showed in the previous section, this boolean expression is logically equivalent to adding both a MaxLength and a MinLength constraint to the street property. The following are some of the pros and cons of this approach:

PROS

  • We could support the full set of boolean expressions that is supported in Java.
  • It is familiar to Java coders.

CONS

  • It is more prone to runtime errors. That is, syntax errors in the expressions will not be caught by compilers and, given that the expressions can be fairly complex, the likelihood of syntax errors is reasonably high.
  • It is more complex to build code that processes it. In contrast to the approach outlined in this article, the boolean expression would in this case need to be parsed and executed. This is not impossible but certainly more complex.
  • If we did support full boolean expressions, then integration with XML Schema would be more complex. Programmers would need to be aware that some of the constraints they added in the Java programming language would be lost when generating XML schema. For example, any expression that used an OR operator could not be mapped to XML Schema.

Incidentally, if we go down this path and add full boolean expression support, we are getting pretty close to what Design By Contract (DBC) implementations like iContract offer. The goal of the constraint mechanism is not to create another DBC solution, but instead to add a simple mechanism for declaratively adding some constraints to properties, which is aligned with important standards like XML Schema and JSF. This is intended to be complimentary to the J2SE 1.4 assertion facility, which provides support for some of the DBC concepts.

So while there are some limitations to the approach we have presented in this article, we believe that it provides a pragmatic and useful solution to an important problem.

Summary

In this article we have discussed the value of having a mechanism in the Java programming language to be able to declaratively add constraints to properties, and covered some of the existing approaches to solving this problem. Additionally, we have listed what we see as some of the more important features of an ideal solution, and presented a new approach, that leverages the power of J2SE 5.0 annotations to, in our opinion, take us a step closer to that ideal.

Resources
  1. The Source Code for this Article (zip) ~32.6 KB | JavaDoc for the Article Source (zip) ~187 KB
  2. The J2SE 5.0 Annotations Feature
  3. JSR 175 - The JSR that defined the annotations feature
  4. J2SE 5.0 in a Nutshell - contains more annotations examples
  5. XML Schema
    http://www.w3.org/XML/Schema
    http://www.w3.org/TR/2001/REC-xmlschema-0-20010502/
    http://www.w3.org/TR/2001/REC-xmlschema-1-20010502/
    http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/
  6. XML Schema simple type facets
    http://www.w3.org/TR/xmlschema-0/#SimpleTypeFacets
  7. Java 2 Enterprise Edition (J2EE)
  8. HTML 4.0
  9. XFORMS
  10. XHTML 2.0
  11. Web Services Description Language (WSDL)
  12. "Validation with Java and XML Schema, Part 1", Brett McLaughlin (JavaWorld, September 2000):
    This article introduces what we have called the "XML Schema Approach".
  13. "Validation with pure Java", Victor Okunev (JavaWorld, December 2000):
    This article introduces what we have called the "JavaBean's PropertyDescriptor's Approach".
  14. "iContract: Design by Contract in Java", Oliver Enseling (JavaWorld, February 2001):
  15. "Hardcore Java", Robert Simmons, Jr
    This book discusses a similar approach to what we have called the "Static Constraint Object Approach".
  16. JSF - Java Server Faces
  17. Struts
  18. Java Data Objects (JDO) - persistence framework
  19. Java XML Binding (JAXB)
  20. Netbeans 4 IDE
  21. Design Patterns: Elements of Reusable Object-Oriented Software
    For more information on the Decorator and other patterns.
  22. The Annotation Processing Tool (apt)
Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.