Jan 19, 2012

Creating a Spring @StrictDateTimeFormat Annotation

Spring Formatters and Converters make it easy to annotate fields for conversion from Objects to Strings, and are especially useful in web apps. But there is no easy or straightforward way to strictly validate the String before parsing into an object, without creating a custom Formatter. Here is a reusable solution that uses a RegexParserDecorator to decorate any Spring Formatter to apply a regex pattern, in turn creating a @StrictDateTimeFormat annotation as an example implementation.

A little background: Spring 3.0 brought the Converter and Formatter framework with a concise @DateTimeFormat annotation, simplifying the Date to Object conversion that previously took custom binders or other wiring code. With @DateTimeFormat you can easily supply a String pattern used to parse and print a Date (or joda DateTime) object. However, the annotation does not strictly validate the String before converting to a Date. For instance, supplying a MM/dd/yyyy pattern does NOT enforce a 4 digit year. Instead a 2 digit year will be accepted and parsed using the SimpleDateFormat rules. Similar loose checking goes for 1 digit days and months, and also the slash character used as a separator. It would be easy to just throw the @Pattern tag onto your Date field except that @Pattern is only allowed on String fields. Combining @Pattern and @DateTimeFormat is what drove the creation of @StrictDateTimeFormat:

//sample usage using defaults for regex and pattern
@StrictDateTimeFormat
private DateTime birthday;

Read more below for a discussion and snippets of code, and the entire codeset with comments is available on github.

The RegexParserDecorator: The first step is to create a Regex Parser class that will apply a regex pattern to validate a String for us. Creating this as a Decorator gives the added benefit that you can easily wrap any Spring Formatter to apply Regex patterns. The constructor takes a Parser to wrap and a regex to apply; and the parse method first validates against the regex before passing onto the decorated Parser:

public RegexParserDecorator(Parser parser, String regex) {
	this.parser = parser;
	this.regexPattern = Pattern.compile(regex);
}
public T parse(String text, Locale locale) throws ParseException {
	if (!regexPattern.matcher(text).matches()) {
		throw new IllegalArgumentException("Text does not match regex: " + text);
	}
	return parser.parse(text, locale);
}

The @StrictDateTimeFormat Annotation: Next step is to setup the annotation interface class. It is very similar to DateTimeFormat but adds the field to hold a regex. The default regex allows 1 or 2 digit days and months, requires a forward slash as the separator, and enforces a 4 digit year. This can be easily overriden when applying the annotation to a field by supplying your own (regex=””, pattern=””) extension. A pattern is still required so make sure your pattern and regex are paired appropriately.

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface StrictDateTimeFormat {
    public static final String REGEX_DEFAULT = "^(0?[1-9]|1[012])/(0?[1-9]|[12][0-9]|3[01])/dddd$";
    public static final String PATTERN_DEFAULT = "MM/dd/yyyy";
    String regex() default REGEX_DEFAULT;
    String pattern() default PATTERN_DEFAULT;
}

Spring then requires a StrictDateTimeFormatAnnotationFormatterFactory to wire the annotation with the parser. Nothing fancy here as it borrows heavily upon Spring’s own JodaDateTimeFormatAnnotationFormatterFactory. The getParser method applies our regex to the DateTimeFormat functionality:

public class StrictDateTimeFormatAnnotationFormatterFactory implements
		AnnotationFormatterFactory {
...
	public Parser getParser(StrictDateTimeFormat annotation, Class fieldType) {
		DateTimeParser parser = new DateTimeParser(forPattern(annotation.pattern()));
		return new RegexParserDecorator(parser, annotation.regex());
	}
	private DateTimeFormatter forPattern(String pattern) {
		return org.joda.time.format.DateTimeFormat.forPattern(pattern);
	}
...

Hooking it all together: Here is the snippet from my applicationConfig.xml showing how the annotation is registered into Spring:

<mvc:annotation-driven conversion-service="myConversionService" />
<bean id="myConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
	<property name="formatters">
		<list>
			<bean class="jeffsheets.util.format.StrictDateTimeFormatAnnotationFormatterFactory" />
		</list>
	</property>
</bean>

Hopefully this information is helpful in creating a reusable regex validating DateTime formatter for use in your own web application.

About the Author

Jeff Sheets profile.

Jeff Sheets

VP - Technology

Jeff has developed Java, Groovy, Grails, and Javascript web apps for industries as varied as Defense, Energy, Weather, Insurance, and Telecom. He is a co-organizer of the Omaha Java Users Group. Jeff has worked on Grails projects since the Grails 1.3.x days, and has experience with production Groovy code as well as Spock tests and Gradle builds. His latest focus has been on AngularJS and Spring Boot applications using JHipster. Jeff also enjoys volunteering at local CoderDojo events to teach programming to our next generation.

One thought on “Creating a Spring @StrictDateTimeFormat Annotation

  1. Stephen Skinner says:

    Hi,

    Excellent article, I’ve implemented this for our web app, much appreciated.

    One thing I found was missing was adding i18n messages as the annotation doesnt follow the standard hibernate annotation message format, it would be an excellent addition to add this at the bottom of your article.

    I found how to at the bottom of this article:
    http://jtuts.com/2014/11/09/validating-dates-in-spring-form-objects/

    Basically need format similar to:
    typeMismatch.playerDetailsModel.player.lastChampionshipGame.footballDate

    Many thanks,
    Stephen

  2. Yanis says:

    Hi Jeff, I tried your solution using Java classes for Spring configuration and I cannot figure out, how the second part i.e. the webflow configuration should be done. I also noticed that the DefaultConversionService of Spring does not have any arguments, while in the xml it is written . I would appreciate if you could give me any hint.

    Cheers,
    Yanis

    1. Jeff Sheets says:

      Hi Yanis,
      Unfortunately the issue could be many things. Perhaps you could share a github code gist and I could take a look there to provide comments?
      — Jeff

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Setup Spring DataSource from values stored in AWS Secret Manager
I was tasked migrating an application to AWS recently. The company wanted the application to store the database credentials in AWS’s Secret Manager. The issue that I had is that if I setup a configuration […]
Up to Spec: JavaScript Numeric Separators
Let's take a look at the proposal to add Numeric Separators to the JavaScript specification.
Using Conftest to Validate Configuration Files
Conftest is a utility within the Open Policy Agent ecosystem that helps simplify writing validation tests against configuration files. In a previous blog post, I wrote about using the Open Policy Agent utility directly to […]
SwiftGen with Image & Color Asset Catalogs
You might remember back in 2015 when iOS 9 was introduced, and we were finally given a way to manage all of our assets in one place with Asset Catalogs. A few years later, support […]