2016-06-03

How to SpEL Validation - Class-Level & Cross-Parameter Constraints with Spring Expression Language

In this article I will describe how to leverage Spring's Expression Language (SpEL) in order to write powerful class-level and cross-parameter constraints using the JSR 349 - (Bean Validation 1.1) reference implementation Hibernate Validator.

First of all, I will briefly introduce Hibernate Validator which is a great implementation of the JSR 349 standard. It can be used to validate your Java classes against certain constraints. Annotations such as @NotNull, @Size, @AssertTrue, @Past or @Future are typical examples for such constraints. These annotations allow you to separate the validation logic from your code using a declarative mechanism. However, once you are getting used to this approach you will soon realise that the standard annotations will not suffice to validate all your domain classes and services. But don't worry, Hibernate Validator allows you the writing of your own custom annotations which I will briefly explain in Part 1 of this article. After introducing the state-of-the art in validation, I will show you the limitations of the approach in Part 2  and present a flexible, powerful and still easy-to-implement strategy based on the Spring Expression Language to support class-level & cross-parameter constraints.

Part 1. Implementing your own validation annotations

It is really desirable to write your own validation annotations because you will be able to express different types of constraints in a declarative way and separate validation logic from business logic. I distinguish the following types of constraints as described in the Hibernate Validation documentation: 
  1. Bean constraints: field-level, property-level, class-level and type argument (since Java 8) constraints
  2. Method constraints: (cross-) parameter and return value constraints
Field-level annotations constrain the value of particular field. Property-level annotations are similar but are annotated on the getter method of a particular field. Class-level annotations constrain the state of an object. This may involve several fields whose values may depend on each other.  Type argument annotations ca be used for validation on Java generic types. This allows you to check for null values in a collection, for example. Parameter annotations can be used to check individual parameters of a method. A more advanced mechanism is to validate against cross-parameter constraints which involve the state of a (sub) set of the method's parameters. Return value annotations can be used to validate the returned value of a non-void method. In the following I will elaborate on field-level, class and cross-parameter constraints.

1.1 Field-level constraints

Lets say we want to implement a new annotation which verifies the minimum age of a user to register for a social network. The validation standard defines two date-related annotations @Past and @Future. They allow you to check whether the date of the annotated attribute lies in the past or respectively in the future. In our case we want to check whether the user has an age of at least x years. To achieve this, we have to implement our own annotation first which we call @ValidAge shown in the following code snippet:

import java.lang.annotation.*;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {AgeValidator.class})
@Documented
public @interface ValidAge {
   String message() default "{age.validation.message}";

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

}

This annotation is targeted at fields or other annotations and the @Constraint annotation defines by which class the validation will be implemented. Furthermore, we define methods groups() for group-based validation (for further information on this topic, please refer to Chapter 5, Grouping constraints of the Hibernate Validator documentation) and message() / payload() for printing messages when validation fails. These are the standard methods supported by Hibernate Validator. Additionally, we define the min() method to allow the user to define the minimum age to be checked. Next, I will show how to implement age validation class containing the validation logic:

import java.util.Calendar;
import java.util.Date;

import javax.validation.ConstraintValidatorContext;

public class AgeValidator implements ConstraintValidator<ValidAge, Date> {
private ValidAge constraintAnnotation;
public void initialize(ValidAge constraintAnnotation) {
this.constraintAnnotation = constraintAnnotation;
}

public boolean isValid(Date value, ConstraintValidatorContext context) {
if (value == null) {
return true;
} else {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.YEAR, -constraintAnnotation.min());
return !cal.getTime().before(value);
}
}

}

The class responsible for the validation must implement the ConstraintValidator interface with type parameters of the annotation and the type of the annotated field (here java.util.Date). It is also possible to have multiple validation implementation classes to support several types of fields. In this example we could also write a validator for attributes of the new java.time.LocalDateTime class. The isValid() method returns true if the validation is successful. We are now ready to use our new annotation in our domain class called User:

public class User {
    @ValidAge(min = 13)
private Date birthday;
}

Spring enables the validation by annotating the bean class with @Validated. You can then annotate method parameters of those beans with @Valid to validate the User object passed as parameter:

@Validated
@Service
public class UserService {
   @Transactional
public User registerUser(@NotNull @Valid User newUser) {
           // Implement register logic here 
   }
}

1.2 Class-level constraints

In some situations field-level annotations are not sufficient, for instance, if you want to validate the state of an object. This often involves more than one attribute and the attribute values may interfere. An example would be the following User class:

public class User {
   @NotNull
private String login;
   @NotNull
   @Email
private String email;

       // Getters and setters for login and email not shown here
}

Let's imagine we don't want to permit null values, neither for login nor for email. However, if we want to check that either login or email must not be null because one of both shall serve as the username (one can be null but not both), we cannot rely on the given set of standard annotations. We need to write our own annotation but this time it must operate on class not on field level.

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { UsernameValidator.class })
@Documented
public @interface ValidUsername {
   String message() default "{username.validation.message}";

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

}

Once again a new validation implementation is needed:

public class UsernameValidator implements ConstraintValidator<ValidUsername, User> {

public void initialize(ValidUsername constraintAnnotation) {
}

public boolean isValid(User user, ConstraintValidatorContext context) {
     return !(user.getLogin() == null && user.getEmail() == null);
}

}

We can use our new annotation to add it to the User class:

@ValidUsername
public class User {

  private String login;
  @Email
private String email;

     // Getters and setters for login and email not shown here
}

1.3 Cross-parameter constraints

Imagine you have a service method which has two parameters from and until --both of type LocalTimeDate-- and you want to validate that the first parameter from is before until

@Validated
@Service
public class UserService {
   @ValidDates
public Collection<User> findUser(LocalDateTime from, LocalDateTime until) {
           // Implement findUser logic here 
   }
}

In order to validate the parameters, you need to define so called cross-parameter constraints. How do you do that? The answer is: just as in the approach above! Start writing a new annotation:

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { DatesValidator.class })
@Documented
public @interface ValidDates {
   String message() default "{dates.validation.message}";

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

}

Then implement the DatesValidator class as follows:

import java.time.LocalDateTime;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class DatesValidator implements ConstraintValidator<ValidDates, Object[]> {

  public void initialize(ValidDates constraintAnnotation) {
 
   }

       public boolean isValid(Object[] value, ConstraintValidatorContext context) {
      if (value.length != 2) {
throw new IllegalArgumentException("Only methods with 2 arguments supported!");
      }
      if (value[0] == null || value[1] == null) {
         return true;
      }
      if (!(value[0] instanceof LocalDateTime) || !(value[1] instanceof LocalDateTime)) {
         throw new IllegalArgumentException("Parameters must be of type LocalDateTime!");
      }
      return ((LocalDateTime) value[0]).isBefore(((LocalDateTime) value[1]));
   }

}

You can extend this class to support other types of dates, such as java.util.Date or java.time.ZonedDateTime as well. 

Part 2. Implementing powerful validation strategies with the Spring Expression Language

In summary, Hibernate Validation enables you to write your own annotations and to define your own class-level constraints quite easily. However, the code tends to get verbose really quickly and thus it does not scale very well. For instance, you have to write new annotations and validation classes for each specific scenario. Whatever new class-level annotation you defined it will be difficult to reuse for different target classes and scenarios. If we would like to support the annotation of another class besides User in our example, we had to write a new UsernameValidator class which would be specific to that newly supported class. One solution would be to define an interface UsernameProvider defining getLogin() and getEmail(). Our validation class would then use this interface as type parameter:

public class UsernameValidator implements ConstraintValidator<UsernameProvider, User>

All classes you are planning to use our @ValidUsername annotation for must then implement the new UsernameProvider interface.

But what if we want to generalize our username validation strategy into a generic @NotAllAttributesNull constraint which checks whether at least one attribute of the annotated class is not null? We could use Object as the type parameter in the validation class and lookup all getter methods via reflection. However, the code based on reflection tends to get complex and difficult to read. Isn't there any better approach? Yes, for sure! Use Spring Expression Language to implement class-level and cross parameters constraints.

2.1 Class-level constraints

Wouldn't it be nice if we could just define one powerful annotation for all class-level constraints and only one validator class? Yes it is possible and it is not very difficult! In the following I will show you how to accomplish that.

Let's start again with a new annotation called @ValidateClassExpression:

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {SpELClassValidator.class})
@Documented
public @interface ValidateClassExpression {

   String message() default "{expression.validation.message}";
   Class<?>[] groups() default { };

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

}

As you can see I've specified an extra attribute called value which allows us to define our concrete (Spring expression language, SpEL) expression. Now have a look on how simple it is to write the validator class based on SpEL:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpELClassValidator implements ConstraintValidator<ValidateClassExpression,   
  Object> {
   private ValidateClassExpression annotation;
   private ExpressionParser parser = new SpelExpressionParser();
   public void initialize(ValidateClassExpression constraintAnnotation) {
      annotation = constraintAnnotation;
      parser.parseExpression(constraintAnnotation.value());
   }

   public boolean isValid(Object value, ConstraintValidatorContext context) {
      StandardEvaluationContext spelContext = new StandardEvaluationContext(value); 
      return (Boolean) parser.parseExpression(annotation.value()).getValue(spelContext);
   }

}

This class is able to evaluate the SpEL expression given by the expression attribute of your annotation and returns the result as the result for your validation! Please make yourself a bit familiar with SpEL to understand how powerful and flexible the application of this approach is (please refer to the Spring docs: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html)

The starting point for your evaluation SpEL's #this variable pointing to the current evaluation object which is the object to be validated in our case. You can access all properties and invoke all methods of the object using SpEL.

Lets come back to our User class and apply the new validator for checking whether login and email are null.

@ValidateClassExpression(value = "!(#this.login == null && #this.email == null)", message = "Login or email must be defined.")
public class User {

private String login;
 @Email
private String email;

}

Great :-). And imagine user has another attribute which is collection of cars. How could we check that the user has at least one car? It is so easy:

@ValidateClassExpression(value = "!#this.cars.isEmpty()", message = "User must have at least one car.")
public class User {

 private List<Car> cars new ArrayList<>();
 // Getters and setters for login and email not shown here
}

We could also combine several expressions by a simple conjunction of both expressions but the downside of this approach is that you could only use one validation message instead of a specific message for each constraint.

@ValidateClassExpression(value = "!(#this.login == null && #this.email == null) && !#this.cars.isEmpty()", message = "Validation failed")

No problem! Since Java 8 it is possible to make annotations repeatable. Thus, we can add the following annotation to the @ValidateClassExpression annotation:

@Repeatable(ValidateClassExpressions.class)

Furthermore, we have to write a new container annotation:

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateClassExpressions {
ValidateClassExpression[] value();
}

This enables us to put multiple ValidateClassExpression annotations to the User class with individual error messages:

@ValidateClassExpression(value = "!(#this.login == null && #this.email == null)", message = "Login or email must be defined.")
@ValidateClassExpression(value = "!#this.cars.isEmpty()", message = "User must have at least one car.")
public class User {

private String login;
 @Email
private String email;

 private List<Car> cars new ArrayList<>();
 // Getters and setters for login, email and cars not shown here
}

Now you have a great toolset to write really flexible and powerful constraints. Last but not least let's have a look at how to apply this concept to cross parameter constraints as well. 

2.2 Cross-parameter constraints

For the validation of cross-parameters we first define a new annotation:

@Constraint(validatedBy = SpELParameterValidator.class)
@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateParametersExpression {
String message() default "{parameters.validation.message}";
Class<?>[] groups() default { };

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

Then, we implement the validation class as follows:

@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class SpELParameterValidator implements ConstraintValidator< ValidateParametersExpression,
   Object[]> {

   private ValidateParametersExpression validParameters;
private ExpressionParser parser = new SpelExpressionParser();
public void initialize(ValidateParametersExpression
 constraintAnnotation) {
this.validParameters = constraintAnnotation;
parser.parseExpression(constraintAnnotation.value());
  }

public boolean isValid(Object[] values, ConstraintValidatorContext context) {
StandardEvaluationContext spelContext = new StandardEvaluationContext(values);
Map<String, Object> spelVars = IntStream.range(0,  
         values.length).boxed().collect(Collectors.toMap(
     i -> "arg" + i,
       i -> values[i]
     ));
spelContext.setVariables(spelVars);
Boolean evaluationValue = (Boolean)  
     parser.parseExpression(validParameters.value()).getValue(spelContext);
return evaluationValue;
  }

}

In contrast to the class-level validation where we accessed only a single object (the one to be validated) we need to access the array of arguments from the method to be validated. For convenience we register the arguments as variables named arg0 - argN. We can now redefine our DatesValidator as follows:

@Validated
@Service
public class UserService {
 @ValidateParametersExpression("#arg0.isBefore(#arg1)")
public Collection<User> findUser(LocalTimeDate from, LocalTimeDate until) {
           // Implement findUser logic here 
 }
}

How powerful and simple! In my opinion simplicity and productivity for writing constraints is inherently important because otherwise you might skip certain validation constraint because of the high implementation effort and this could lead to illegal states in the future.

I hope you found this article helpful and it inspired you to combine the Spring Expression language with Hibernate Validator in the future. The two annotations that we've implemented using SpEL @ValidClassExpression and @ValidParametersExpression can be used for all kinds of use cases related to class-level or cross-parameter validation. This allows you to apply validation to all your classes and methods which will definitely increase your code quality in the long run and helps you to separate your validation from your business code.

I am looking forward to any comments, improvements or extensions to the presented approach. Thanks for reading.  You can checkout the code examples presented in this article from my Github repository:  https://github.com/Javatar81/code-examples/tree/master/spelvalidation

13 comments:

  1. Very good article. I think you might want to change the blog interface to English. Currently, the comments section and the Archive section is in German.

    ReplyDelete
  2. I am really impressed how much code you're willing to write to avoid a simple EmailAddress and DateRange class to actually use the type system to make sure correctness.

    How can code that deals with a User be sure the email String was validated. If you instead use an EmailAddress object and reject invalid values upon construction, you'd never even get into the situation that you could create a user without a valid email address.

    The same applies to the service method. If there's inter-parameter assertions to be made, put them into a type. Your hiding the concept of a date range in an annotation and code put into String based expression. Just introduce a DateRange value type and make sure it doesn't even allow invalid dates to be used in the first place. Not only could the service code actually be sure, that it gets a valid date range (with your current code, I can still easily invoke the method with invalid parameters), also you'de make the domain concept explicit.

    I highly recommend to watch this presentation for how the use of value objects can dramatically improve readability of code and at the same time make it less error prone: https://www.infoq.com/presentations/Value-Objects-Dan-Bergh-Johnsson

    ReplyDelete
    Replies
    1. Thanks for your comment. I think you raised some really interesting points regarding the validation topic in general. However, my intention of writing this article was just to improve productivity of developers who heavily rely on Bean Validation / Hibernate Validator. The article's goal was not to discuss the approach of validation via annotations in general. Nonetheless, I think your comment is valuable for all readers of this post because it discusses whether you should generally rely on or avoid Bean Validation/ JSR 349 for validating beans and methods.

      As you said, with the value object approach you can easily and instantly assure (upon object creation) that the created object is valid. However, this can also be a downside because you might want to allow some temporary intermediate state which is invalid (for example, if you have a wizard-like web form with several steps and your application allows you to save temporary data of one step without validation but accepts only valid data if you continue to the next step of the wizard). The advantage of Bean Validation is that you can decide when to validate your data, e.g., whenever someone calls your service method or when you persist your data.

      I disagree with you that I need much code for my proposed approach because at the end you will have 4 small classes for class-level and cross-parameter validation, the remainder is already provided by the Hibernate reference implementation. I also disagree with your argument that using a string-based expression is a problem in this context. In general, it is better to rely on the compiler’s type checking facilities - for sure, however, the effort is much higher because you have to write new code for every validation scenario (or if you rely on reflection you don’t have type safety). At the end it is your decision, more code and type safety (less error-prone and easier to refactor) or less code (easier to read and understand). Furthermore with Bean Validation, you can select the best approach depending on the value(s) to validate. Firstly, you can make use of the standard JSR 349 annotations. Secondly, you can use Hibernate’s own additional annotations (such as @Email) and thirdly, you can write your own validation annotation either with custom Java code (with type safety) or using the Spring Expression Language (less code but less type safety, btw. there are Eclipse plugins supporting code completion and syntax check for SpEL). At the end I would say it is somehow a matter of taste. I see validation as a cross-cutting concern such as security or logging which I would try to separate from the domain model and business logic. I would prefer to have value objects with just data and some convenience methods to access this data rather than adding validation logic to those objects. My experience is that it is also much easier to avoid code duplication (you have only one implementation class for one type of validation) as you will find similar or even the same patterns for validation in many different classes to be validated.

      Delete
    2. First of, thanks for the great post. I've spent too much time already writing silly validator implementations.

      I can only agree that in many cases, value types are not a good option. Validation is usually a "soft" requirement. Imagine a UI where the user selects a date range and you want to make sure the first is before the second date. You could use a value type for that. Then you would have to catch an IllegalArgumentException (always a bad idea) and inform the user. And you would have to do these try-catch blocks for everything you do on your UI. And to top it off, you don't have a reasonable state in your model, as you couldn't create some of your values.

      Or you use validation. Instead of catching runtime exceptions everywhere, you validate once. The result tells you where an error was found and gives you a message for the user. It's much more flexible and leaves your model in a valid state.

      Or imagine a Hibernate Entity. These get validated automatically on storing them in a DB. You could, of course, use value types. And then you define for every value type how it gets stored into the DB, either by converting it or by making it an embeddable entity itself. And again you catch exceptions.

      Even for a service interface, value types are not always better. When using some RPC framework like Hessian, you need to share the code for the value types. If you use REST, it's worse. Sharing code would be plain stupid, but otherwise you cannot guarantee that whatever JSON or XML gets sent was created correctly before it arrives.

      This is by no means meant as a rant against data types in general. I like them quite a bit, especially if they're immutable. But for ensuring constraints on mostly unknown input, they're not a good option. Your system would become too fragile.

      Delete
  3. Nice.

    Note that this kind of thing is not part of the Bean Validation specification because you do lose all of the metadata aspect of the BV API (as well as type safety). But that's an OK compromise to get by fast at the cost of a little debt.

    ReplyDelete
  4. Before this article, I have little bit doubt about the concept of leverage Spring's Expression Language validation, but after reading this post I am very much clear about it. I was looking for such kind of post. Thanks for sharing.

    ReplyDelete
  5. Good article. I like all these ideas except for having to write the validation code in SpEL. SpEL code does not refactor, so whenever I modify my beans I would be forced into text search to find and fix code. That doesn't scale and is a dealbreaker for me.

    What I REALLY want to do is move "!(#this.login == null && #this.email == null)" into a java method. This would allow refactoring to do its magic.

    public boolean validateMethod(){
    return !(login == null && email == null);
    }

    But where should this method reside? In the User class or the UserValidator class? If in the UserValidator class, we are forced to use interfaces such as UsernameProvider. In the User class, our methods are now booleans, so we find ourselves reporting that "the property checkLoginAndEmailForNull was equal to false", rather than "Login or email must be defined."

    ReplyDelete
    Replies
    1. I agree with you about the drawbacks regarding code refactorings which is caused by the use of a text-based expression language as the constraint definition. It is a kind of trade-off and where to use this approach depends on the context. I my opinion it is really useful for simple constraints because the approach is light-weight and constraints show up just in place (on top of the validated classes). For more complex constraints a separate ConstraintValidator class might be more beneficial.

      On one hand, the source of the refactoring problem lies in the lack of type safety in the text-based SpEL. On the other hand, this enables us to write constraints which can be applied to all types of classes as long as they possess, in this example, the attributes login and email.

      The question you raise in your last paragraph is really an interesting one. Where should the method and thus the class-level constraint reside?

      Should it be just a simple method of the class to be validated as you proposed with User?
      Pro: Easy to implement. Con: No clear separation of validation code from business methods. No extra features from validation framework (such as validation messages etc.)

      Should the validation logic stay in its own Validator class and be referenced by the annotation (as it is implemented in the Hibernate Validator examples)?
      Pro: Clear separation. Con: Much more code to write.

      Should the validated class be annotated (as with Hibernate) but the annotation will directly hold the (simple) validation logic as I've presented with SpEL?
      Pro: Moderate Separation. Simple to implement. Code does not depend on specific class. Con: No type-safety and thus no refactoring support.

      A nice idea would be to define a validation method in an arbitrary class and then reference it by using Java 8 method references:

      @LambdaValidate(UsefulValidators:: checkLoginAndEmailForNull)

      Unfortunately it is currently not allowed to use Method references nor lambdas as the attribute value of a Java annotation.


      Delete
    2. For the problem statement you highlighted in respect to writing SPEL could be dealt if unit test cases are written along. Any changes to your object will make the test case fail and you would know what to update.

      Delete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. This comment has been removed by a blog administrator.

    ReplyDelete
  8. This comment has been removed by a blog administrator.

    ReplyDelete
  9. This comment has been removed by a blog administrator.

    ReplyDelete