Question

I worked out a concept to conditionally validate using JSR 303 groups. "Conditionally" means that I have some fields which are only relevant if another field has a specific value.

Example: There is an option to select whether to register as a person or as a company. When selecting company, the user has to fill a field containing the name of the company.

Now I thought I use groups for that:

  class RegisterForm
  {
     public interface BasicCheck {}
     public interface UserCheck {}
     public interface CompanyCheck {}

     @NotNull(groups = BasicCheck.class)
     private Boolean isCompany

     @NotNull(groups = UserCheck.class)
     private String firstName;

     @NotNull(groups = UserCheck.class)
     private String lastName;

     @NotNull(groups = CompanyCheck.class)
     private String companyName;

     // getters / setters ...
  }

In my controller, I validate step by step depending on the respective selection:

  @Autowired
  SmartValidator validator;

  public void onRequest(@ModelAttribute("registerForm") RegisterForm registerForm, BindingResult result)
  {
     validator.validate(registerForm, result, RegisterForm.BasicCheck.class);
     if (result.hasErrors()
        return;
     // basic check successful => we can process fields which are covered by this check
     if (registerForm.getIsCompany())
     {
        validator.validate(registerForm, result, RegisterForm.CompanyCheck.class)
     }
     else
     {
        validator.validate(registerForm, result, RegisterForm.UserCheck.class);
     }
     if (!result.hasErrors())
     {
        // process registration
     }
  }

I only want to validate what must be validated. If the user selects "company" fills a field with invalid content and then switches back to "user", the invalid company related content must be ignored by the validator. A solution would be to clear those fields using Javascript, but I also want my forms to work with javascript disabled. This is why I totally like the approach shown above.

But Spring breaks this idea due to data binding. Before validation starts, Spring binds the data to registerForm. It adds error to result if, for instance, types are incompatible (expected int-value, but user filled the form with letters). This is a problem as these errors are shown in the JSP-view by <form:errors /> tags

Now I found a way to prevent Spring from adding those errors to the binding result by implementing a custom BindingErrorProcessor. If a field contains null I know that there was a validation error. In my concept null is not allowed - every field gets annotated with @NotNull plus the respective validation group.

As I am new to Spring and JSR-303 I wonder, whether I am totally on the wrong path. The fact that I have to implement a couple of things on my own makes me uncertain. Is this a clean solution? Is there a better solution for the same problem, as I think this is a common problem?

EDIT

Please see my answer here if you are interested in my solution in detail: https://stackoverflow.com/a/30500985/395879

Was it helpful?

Solution

You are correct that Spring MVC is a bit picky in this regard,and it is a common problem. But there are work-arounds:

  • Make all your backing fields strings, and do number/date etc conversions and null checks manually.
  • Use JavaScript to set fields to null when they become irrelevant.
  • Use JavaScript to validate fields when they are entered. This will fix almost all of your problems.

Good luck!

OTHER TIPS

I know this question is old, but I came upon it looking for an answer for a different situation.

I think for your situation you could use inheritance for the forms and then use two controller methods:

The forms would look like this:

public class RegistrationForm
{
    // Common fields go here.
}

public class UserRegistrationForm
    extends RegistrationForm
{
    @NotNull
    private String firstName;

    @NotNull
    private String lastName;

    // getters / setters ...
}

public class CompanyRegistrationForm
    extends RegistrationForm
{
    @NotNull
    private String companyName;

    // getters / setters ...
}

The controller methods would look like this:

@RequestMapping(method = RequestMethod.POST, params = "isCompany=false")
public void onRequest(
        @ModelAttribute("registerForm") @Valid UserRegistrationForm form,
        BindingResult result)
{
    if (!result.hasErrors())
    {
        // process registration
    }
}
@RequestMapping(method = RequestMethod.POST, params = "isCompany=true")
public void onRequest(
        @ModelAttribute("registerForm") @Valid CompanyRegistrationForm form,
        BindingResult result)
{
    if (!result.hasErrors())
    {
        // process registration
    }
}

Notice that the @RequestMapping annotations include a params attribute so the value of the isCompany parameter determines which method is called.

Also notice that the @Valid annotation is place on the form parameter.

Finally, no groups are needed in this case.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top