Enterprise Java

Spring MVC Custom Validation Annotations

Last tutorial, I showed how to validate a form using annotations. This works great for simple validations, but eventually, you’ll need to validate some custom rules that aren’t available in the out-of-the-box annotations. For example, what if you need to validate that a user is over 21 years old, calculated based off their input birthdate, or, maybe you need to validate that the user’s phone area code is in Nebraska, USA. This tutorial with full source code will show how to create custom validation annotations that you can use along-side the JSR-303 and Hibernate Validator annotations we explored in the last tutorial.

You can grab the code for this tutorial on GitHub if you want to follow along.

For this example, let’s say we have a form with a phone number field and a birthdate field, and we want to validate the the phone number is valid (simple check for format) and that the user was born in 1989. There are no out-of-the-box annotations that support these (as far as I know), so we will write custom validation annotations which we can then re-use, just like the built-in JSR-303 ones.

When we are done, we will apply our annotations to our form object, like so:

public class Subscriber {

    ...

	@Phone
	private String phone;

	@Year(1989)
	private Date birthday;

    // getters setters ...

}

Let’s get started with the @Phone annotation. We will be creating two classes: Phone, which is the annotation, and PhoneConstraintValidator which contains the validation logic. The first step is to create the Phone annotation class:

@Documented
@Constraint(validatedBy = PhoneConstraintValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {

	String message() default "{Phone}";

	Class<?>[] groups() default {};

	Class<? extends Payload>[] payload() default {};

}

The code above is mostly just boiler-plate. The three methods in the annotation are required by the JSR-303 spec. If our annotation accepted any arguments, we would have defined them there as methods. We will see this in our next annotation later in this tutorial. The most important part of the class above is the @Constraint annotation on the class which specifies that we will use our PhoneConstraintValidator class for the validation logic. The message() method defines how the message is resolved. By specifying “{Phone}”, we can override the message in a Spring resource bundle using the Phone key (see my other validation tutorial for details about messages).

Now, we define the constraint validator:

public class PhoneConstraintValidator implements ConstraintValidator<Phone, String> {

	@Override
	public void initialize(Phone phone) { }

	@Override
	public boolean isValid(String phoneField, ConstraintValidatorContext cxt) {
		if(phoneField == null) {
			return false;
		}
		return phoneField.matches("[0-9()-\.]*");
	}

}

Let’s look at the above code. The templated type of the superclass takes two types: the type of the annotation it supports, and the type of the property it validates (in this example, Phone, String).

The “initialize” method is empty here, but it can be used to save data from the annotation, as we will see below when we define our other annotation.

Finally, the actual logic happens in the “isValid” method. The field value is passed in as the first argument, and we do our validation here. As you can see, I am just validating that the phone number only contains numbers, parentheses or dashes.

That’s it for this annotation! The annotation can now be used on a field as shown above on our form object.

Now, let’s do our second annotation. This one is a little contrived – we will validate that the user’s birthdate is in 1989. In the future, we may need to validate dates are in other years, though, so rather than create an annotation that validates the year to be 1989, we will let it take an argument to specify the year to validate against. Example usage:

@Year(1989)
private Date birthDate;

Now, the annotation:

@Documented
@Constraint(validatedBy = YearConstraintValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Year {

	int value();

	String message() default "{Year}";

	Class<?>[] groups() default {};

	Class<? extends Payload>[] payload() default {};

}

Notice the “value()” method. This exposes the “value” argument of the annotation which we will use to pass the year that the annotation should validate against. The rest of the code is mostly boilerplate

Now, the constraint validator:

public class YearConstraintValidator implements ConstraintValidator<Year, Date> {

	private int annotationYear;

	@Override
	public void initialize(Year year) {
		this.annotationYear = year.value();
	}

	@Override
	public boolean isValid(Date target, ConstraintValidatorContext cxt) {
		if(target == null) {
			return true;
		}
		Calendar c = Calendar.getInstance();
		c.setTime(target);
		int fieldYear = c.get(Calendar.YEAR);
		return fieldYear == annotationYear;
	}

}

The first thing to notice, is that, this time, we are saving the year passed into the annotation as a member variable of the constraint validator class. This allows us to access the value in our “isValid” method.

The isValid method is pretty straightforward code wrestling with the obnoxious Date/Calendar API’s to validate that the value of the annotated field matches the year that the validation annotation specified (I may post an example using JodaTime sometime if I get around to it). And now, if we start up our web application, our two validations are in place and ready to be used!

custom-validation-example-e1372733063855

That’s all. Did I miss anything? Have questions? Let me know in the comments.

Full Source: ZIPGitHub
To run the code from this tutorial: Must have Gradle installed. Clone the GitHub repo or download the ZIP and extract. Open command prompt to code location. Run gradle jettyRunWar. Navigate in browser to http://localhost:8080.
 

Reference: Spring MVC Custom Validation Annotations from our JCG partner Steve Hanson at the CodeTutr blog.

Steve Hanson

Steve is a software developer interested in web development and new technologies. He currently works as a Java consultant at Credera in Dallas, TX.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Sumit
Sumit
10 years ago

Nice post.
How can i add additional attributes to my custom annotation ?
For ex Year(day=””, month=””)

Akash
Akash
10 years ago

Can you send me simple explained example of spring validation without hibernate?

Back to top button