164

I would like to know what is the cleanest and best way to perform form validation of user inputs. I have seen some developers implement org.springframework.validation.Validator. A question about that: I saw it validates a class. Does the class have to be filled manually with the values from the user input, and then passed to the validator?

I am confused about the cleanest and best way to validate the user input. I know about the traditional method of using request.getParameter() and then manually checking for nulls, but I don't want to do all the validation in my Controller. Some good advice on this area will be greatly appreciated. I am not using Hibernate in this application.

patstuart
  • 1,931
  • 1
  • 19
  • 29
devdar
  • 5,564
  • 28
  • 100
  • 153

7 Answers7

338

With Spring MVC, there are 3 different ways to perform validation : using annotation, manually, or a mix of both. There is not a unique "cleanest and best way" to validate, but there is probably one that fits your project/problem/context better.

Let's have a User :

public class User {

    private String name;

    ...

}

Method 1 : If you have Spring 3.x+ and simple validation to do, use javax.validation.constraints annotations (also known as JSR-303 annotations).

public class User {

    @NotNull
    private String name;

    ...

}

You will need a JSR-303 provider in your libraries, like Hibernate Validator who is the reference implementation (this library has nothing to do with databases and relational mapping, it just does validation :-).

Then in your controller you would have something like :

@RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, @Valid @ModelAttribute("user") User user, BindingResult result){
    if (result.hasErrors()){
      // do something
    }
    else {
      // do something else
    }
}

Notice the @Valid : if the user happens to have a null name, result.hasErrors() will be true.

Method 2 : If you have complex validation (like big business validation logic, conditional validation across multiple fields, etc.), or for some reason you cannot use method 1, use manual validation. It is a good practice to separate the controller’s code from the validation logic. Don't create your validation class(es) from scratch, Spring provides a handy org.springframework.validation.Validator interface (since Spring 2).

So let's say you have

public class User {

    private String name;

    private Integer birthYear;
    private User responsibleUser;
    ...

}

and you want to do some "complex" validation like : if the user's age is under 18, responsibleUser must not be null and responsibleUser's age must be over 21.

You will do something like this

public class UserValidator implements Validator {

    @Override
    public boolean supports(Class clazz) {
      return User.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
      User user = (User) target;

      if(user.getName() == null) {
          errors.rejectValue("name", "your_error_code");
      }

      // do "complex" validation here

    }

}

Then in your controller you would have :

@RequestMapping(value="/user", method=RequestMethod.POST)
    public createUser(Model model, @ModelAttribute("user") User user, BindingResult result){
        UserValidator userValidator = new UserValidator();
        userValidator.validate(user, result);

        if (result.hasErrors()){
          // do something
        }
        else {
          // do something else
        }
}

If there are validation errors, result.hasErrors() will be true.

Note : You can also set the validator in a @InitBinder method of the controller, with "binder.setValidator(...)" (in which case a mix use of method 1 and 2 would not be possible, because you replace the default validator). Or you could instantiate it in the default constructor of the controller. Or have a @Component/@Service UserValidator that you inject (@Autowired) in your controller : very useful, because most validators are singletons + unit test mocking becomes easier + your validator could call other Spring components.

Method 3 : Why not using a combination of both methods? Validate the simple stuff, like the "name" attribute, with annotations (it is quick to do, concise and more readable). Keep the heavy validations for validators (when it would take hours to code custom complex validation annotations, or just when it is not possible to use annotations). I did this on a former project, it worked like a charm, quick & easy.

Warning : you must not mistake validation handling for exception handling. Read this post to know when to use them.

References :

kklw
  • 858
  • 3
  • 13
  • 28
Jerome Dalbert
  • 10,067
  • 6
  • 56
  • 64
  • can you tell me what should my servlet.xml should have for this configuration. I want to pass the errors back to the view – devdar Aug 31 '12 at 22:58
  • @dev_darin You mean config for JSR-303 validation ? – Jerome Dalbert Sep 01 '12 at 09:26
  • yes JSR-303 validation i re-accepted it sorry i now realized you can only accept one answer.steve.hanson answer was great as well, was just trying to be fair to two knowledgeable people – devdar Sep 01 '12 at 17:04
  • 2
    @dev_marin For validation, in Spring 3.x+, there is nothing special in "servlet.xml" or "[servlet-name]-servlet.xml. You just need hibernate-validator jar in your project libraries (or via Maven). That's all, it should work then. Warning if you use Method 3 : by default, each controller have access to a JSR-303 validator, so be careful not to override it with "setValidator". If you want to add a custom validator on top, just instantiate it and use it, or inject it (if it is a Spring component). If you still have problems after checking google and Spring doc, you should post a new question. – Jerome Dalbert Sep 02 '12 at 10:42
  • 2
    For the mix use of method 1 & 2, there is a way to use @InitBinder. Instead of "binder.setValidator(...)", can use " binder.addValidators(...)" – jasonfungsing Nov 04 '14 at 22:30
  • 1
    Correct me if I'm wrong, but you can mix validation via JSR-303 annotations (Method 1) and custom validation (Method 2) when using @InitBinder annotation. Simply use binder.addValidators(userValidator) instead of binder.setValidator(userValidator) and both Validation methods will take effect. – SebastianRiemer Aug 20 '15 at 08:42
  • is Bean Validation only available for controller methods(annotated with @RequestMapping)? I use this with a plain method, it doesn't work. – DiveInto Jun 15 '16 at 05:37
  • Thanks for clarify there's 'no clean and best' way to do complex business rules, it saves me time for googling that business validation bible,lol – ChengWhyNot Sep 19 '18 at 06:46
31

There are two ways to validate user input: annotations and by inheriting Spring's Validator class. For simple cases, the annotations are nice. If you need complex validations (like cross-field validation, eg. "verify email address" field), or if your model is validated in multiple places in your application with different rules, or if you don't have the ability to modify your model object by placing annotations on it, Spring's inheritance-based Validator is the way to go. I'll show examples of both.

The actual validation part is the same regardless of which type of validation you're using:

RequestMapping(value="fooPage", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("foo") Foo foo, BindingResult result, ModelMap m) {
    if(result.hasErrors()) {
        return "fooPage";
    }
    ...
    return "successPage";
}

If you are using annotations, your Foo class might look like:

public class Foo {

    @NotNull
    @Size(min = 1, max = 20)
    private String name;

    @NotNull
    @Min(1)
    @Max(110)
    private Integer age;

    // getters, setters
}

Annotations above are javax.validation.constraints annotations. You can also use Hibernate's org.hibernate.validator.constraints, but it doesn't look like you are using Hibernate.

Alternatively, if you implement Spring's Validator, you would create a class as follows:

public class FooValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return Foo.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {

        Foo foo = (Foo) target;

        if(foo.getName() == null) {
            errors.rejectValue("name", "name[emptyMessage]");
        }
        else if(foo.getName().length() < 1 || foo.getName().length() > 20){
            errors.rejectValue("name", "name[invalidLength]");
        }

        if(foo.getAge() == null) {
            errors.rejectValue("age", "age[emptyMessage]");
        }
        else if(foo.getAge() < 1 || foo.getAge() > 110){
            errors.rejectValue("age", "age[invalidAge]");
        }
    }
}

If using the above validator, you also have to bind the validator to the Spring controller (not necessary if using annotations):

@InitBinder("foo")
protected void initBinder(WebDataBinder binder) {
    binder.setValidator(new FooValidator());
}

Also see Spring docs.

Hope that helps.

stephen.hanson
  • 9,014
  • 2
  • 47
  • 53
  • when using Spring's Validator do i have to set the pojo from the controller and then validate it? – devdar Aug 27 '12 at 19:35
  • I'm not sure I understand your question. If you see the controller code snippet, Spring is automatically binding the submitted form to the `Foo` parameter in the handler method. Can you clarify? – stephen.hanson Aug 27 '12 at 19:46
  • ok what i am saying is when the user submits the user inputs the Controller get the http request, from there what happens is it that you use the request.getParameter() to get all the user parameters then set the values in the POJO then pass the class to the validation object. The validation class will send the errors back to the view with the errors if any found. Is this the way it happens? – devdar Aug 27 '12 at 20:04
  • 1
    It would happen like this but there is a simpler way... If you use JSP and a submission, the data are automatically put in the @ModelAttribute("user") user in the controller method. See the doc : http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/view.html – Jerome Dalbert Aug 27 '12 at 20:46
  • +1 because that's the first example I found which uses @ModelAttribute; without it none of the tutorial I found works. – Riccardo Cossu Apr 22 '14 at 13:08
  • Why but why doing a `new CustomValidator()`? Best is to reference a bean of our and inject it with spring context `@Autowired CustomValidator customValidator;` so these way you are able to get the best of IoC container. – BendaThierry.com Aug 29 '16 at 09:01
  • Article link is DOA: _"Error establishing a database connection"_ – Madbreaks Feb 26 '18 at 22:09
  • @Madbreaks thanks. I just took my old site down recently. Removed the link. – stephen.hanson Feb 27 '18 at 21:33
13

I would like to extend nice answer of Jerome Dalbert. I found very easy to write your own annotation validators in JSR-303 way. You are not limited to have "one field" validation. You can create your own annotation on type level and have complex validation (see examples below). I prefer this way because I don't need mix different types of validation (Spring and JSR-303) like Jerome do. Also this validators are "Spring aware" so you can use @Inject/@Autowire out of box.

Example of custom object validation:

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { YourCustomObjectValidator.class })
public @interface YourCustomObjectValid {

    String message() default "{YourCustomObjectValid.message}";

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

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

public class YourCustomObjectValidator implements ConstraintValidator<YourCustomObjectValid, YourCustomObject> {

    @Override
    public void initialize(YourCustomObjectValid constraintAnnotation) { }

    @Override
    public boolean isValid(YourCustomObject value, ConstraintValidatorContext context) {

        // Validate your complex logic 

        // Mark field with error
        ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
        cvb.addNode(someField).addConstraintViolation();

        return true;
    }
}

@YourCustomObjectValid
public YourCustomObject {
}

Example of generic fields equality:

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

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

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { FieldsEqualityValidator.class })
public @interface FieldsEquality {

    String message() default "{FieldsEquality.message}";

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

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

    /**
     * Name of the first field that will be compared.
     * 
     * @return name
     */
    String firstFieldName();

    /**
     * Name of the second field that will be compared.
     * 
     * @return name
     */
    String secondFieldName();

    @Target({ TYPE, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    public @interface List {
        FieldsEquality[] value();
    }
}




import java.lang.reflect.Field;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;

public class FieldsEqualityValidator implements ConstraintValidator<FieldsEquality, Object> {

    private static final Logger log = LoggerFactory.getLogger(FieldsEqualityValidator.class);

    private String firstFieldName;
    private String secondFieldName;

    @Override
    public void initialize(FieldsEquality constraintAnnotation) {
        firstFieldName = constraintAnnotation.firstFieldName();
        secondFieldName = constraintAnnotation.secondFieldName();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null)
            return true;

        try {
            Class<?> clazz = value.getClass();

            Field firstField = ReflectionUtils.findField(clazz, firstFieldName);
            firstField.setAccessible(true);
            Object first = firstField.get(value);

            Field secondField = ReflectionUtils.findField(clazz, secondFieldName);
            secondField.setAccessible(true);
            Object second = secondField.get(value);

            if (first != null && second != null && !first.equals(second)) {
                    ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
          cvb.addNode(firstFieldName).addConstraintViolation();

          ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
          cvb.addNode(someField).addConstraintViolation(secondFieldName);

                return false;
            }
        } catch (Exception e) {
            log.error("Cannot validate fileds equality in '" + value + "'!", e);
            return false;
        }

        return true;
    }
}

@FieldsEquality(firstFieldName = "password", secondFieldName = "confirmPassword")
public class NewUserForm {

    private String password;

    private String confirmPassword;

}
michal.kreuzman
  • 12,170
  • 10
  • 58
  • 70
  • 1
    I was also wondering a controller usually has one validator and i saw where you can have multiple validators however if you have a set of validation defined for one object however the operation you want to preform on the object is different e.g. a save / update for a save a certain set of validation is required and an update a different set of validation is required. Is there a way to configure the validator class to hold all the validation based on the operation or will you required to use multiple validators? – devdar May 10 '13 at 15:19
  • 1
    You can also have a annotation validation on method. So you can create your own "domain validation" if I understand your question. For this you must specify `ElementType.METHOD` in `@Target`. – michal.kreuzman May 10 '13 at 17:54
  • i understand what youre saying can you also point me to an example for a more clear picture. – devdar May 12 '13 at 17:12
5

If you have same error handling logic for different method handlers, then you would end up with lots of handlers with following code pattern:

if (validation.hasErrors()) {
  // do error handling
}
else {
  // do the actual business logic
}

Suppose you're creating RESTful services and want to return 400 Bad Request along with error messages for every validation error case. Then, the error handling part would be same for every single REST endpoint that requires validation. Repeating that very same logic in every single handler is not so DRYish!

One way to solve this problem is to drop the immediate BindingResult after each To-Be-Validated bean. Now, your handler would be like this:

@RequestMapping(...)
public Something doStuff(@Valid Somebean bean) { 
    // do the actual business logic
    // Just the else part!
}

This way, if the bound bean was not valid, a MethodArgumentNotValidException will be thrown by Spring. You can define a ControllerAdvice that handles this exception with that same error handling logic:

@ControllerAdvice
public class ErrorHandlingControllerAdvice {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public SomeErrorBean handleValidationError(MethodArgumentNotValidException ex) {
        // do error handling
        // Just the if part!
    }
}

You still can examine the underlying BindingResult using getBindingResult method of MethodArgumentNotValidException.

Ali Dehghani
  • 46,221
  • 15
  • 164
  • 151
1

Find complete example of Spring Mvc Validation

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.technicalkeeda.bean.Login;

public class LoginValidator implements Validator {
    public boolean supports(Class aClass) {
        return Login.class.equals(aClass);
    }

    public void validate(Object obj, Errors errors) {
        Login login = (Login) obj;
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
                "username.required", "Required field");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userPassword",
                "userpassword.required", "Required field");
    }
}


public class LoginController extends SimpleFormController {
    private LoginService loginService;

    public LoginController() {
        setCommandClass(Login.class);
        setCommandName("login");
    }

    public void setLoginService(LoginService loginService) {
        this.loginService = loginService;
    }

    @Override
    protected ModelAndView onSubmit(Object command) throws Exception {
        Login login = (Login) command;
        loginService.add(login);
        return new ModelAndView("loginsucess", "login", login);
    }
}
Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
Vicky
  • 9,515
  • 16
  • 71
  • 88
0

Put this bean in your configuration class.

 @Bean
  public Validator localValidatorFactoryBean() {
    return new LocalValidatorFactoryBean();
  }

and then You can use

 <T> BindingResult validate(T t) {
    DataBinder binder = new DataBinder(t);
    binder.setValidator(validator);
    binder.validate();
    return binder.getBindingResult();
}

for validating a bean manually. Then You will get all result in BindingResult and you can retrieve from there.

praveen jain
  • 778
  • 2
  • 8
  • 23
0

Validation groups

Also it is worth to mention validation for some more complex cases, when you have some "multi steps" within your business logic. In such cases we need "validation groups".

@Validated annotation was added to support "validation groups" in validated bean. This can be used in multi step forms where in the first step you need, for example, validate name and email, and in the second step you need to validate, for example, phone number.

With @Validated you first need to declare groups. Groups are declared with your custom marker interfaces.


@Validated example

Let's say we have a scenario when we have a form for user sign up. On this form we want user to provide a name and email. And after user is signed up we have another form where we suggest the user to add his some extra information, for example, email. We don't want email be provided on the first step. But it is required to provide it on the second step.

For this case, we'll declare two groups. First group would be OnCreate, and the second group would be OnUpdate :

OnCreate:

public interface OnCreate {}

OnUpdate:

public interface OnUpdate {}

Our user UserAccount class:

public class UserAccount {

    // we will return this field after User is created
    // and we want this field to be provided only on update
    // so we can determine which user needs to be updated
    @NotBlank(groups = OnUpdate.class)
    private String id;

    @NotBlank(groups = OnCreate.class)
    private String name;
   
    @NotBlank(groups = OnCreate.class)
    private String email;
 
    @NotBlank(groups = OnUpdate.class)
    private String phone;
    
    // standard constructors / setters / getters / toString   
    
}

We mark the validation annotations with our groups interfaces depending on which group those validations are supposed to be related.

And finally our Controller methods:

@PostMapping(value = "/create")
public UserAccount createAccount(@Validated(OnCreate.class) @RequestBody UserAccount userAccount) {
    ...
}

@PatchMapping(value = "/update")
public UserAccount updateAccount(@Validated(OnUpdate.class) @RequestBody UserAccount userAccount) {
    ...
}

Here we specify @Validated(...) instead of @Valid and specify the validation group which should be used in different cases.

Now depending on validation group we'll perform the validations for the particular fields within different steps.

kerbermeister
  • 2,985
  • 3
  • 11
  • 30