GroupSequence combined with Valid does't work in all cases


#1

Hello,

I’m using GroupSequence to group constraints into simple and complex validations.
My goal is, that the complex validations are only performed, when the validation of the simple constraints did succeed, so that the complex validations assume that the simple constraints are met.

When combining GroupSequence with Valid to perform validations recursively, the conditional execution of the complex validations doesn’t work as I would expect.

In some cases the complex validations are performed, even if a recursive validation would find a violation of a simple constraint. This leads to a NullPointerException in the complex validation.

Here is an example:

import java.util.Arrays;
import java.util.List;

import javax.validation.GroupSequence;
import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.junit.Assert;
import org.junit.Test;

public class ValidationTest {
  public interface ComplexValidations {}

  @GroupSequence({ Whole.class, ComplexValidations.class })
  public static class Whole {

    @NotNull @Size(min=2, max=10) @Valid
    public List<Part> parts;

    @AssertTrue(groups = ComplexValidations.class)
    public boolean isvalidSum() {
      return parts.stream().mapToInt(part -> part.fraction).sum() == 100;
    }

  }

  private static class Part {
    @NotNull
    public Integer fraction;
  }

  @Test
  public void test() {
    Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    Whole whole = new Whole();
    Part part = new Part();
    part.fraction = null;

    //size is too small, and parts[0].fraction is null, so ComplexValidations aren't performed
    whole.parts = Arrays.asList(part);
    String violations = validator.validate(whole).toString();
    Assert.assertTrue(violations.contains("must not be null', propertyPath=parts[0].fraction"));
    Assert.assertTrue(violations.contains("size must be between 2 and 10', propertyPath=parts"));

    //size is now valid, but parts[0].fraction is still null
    //so ComplexValidations should't be performed
    whole.parts = Arrays.asList(part, part);
    //NPE in isvalidSum because fraction is null and ComplexValidations are still performed
    violations = validator.validate(whole).toString();
    Assert.assertTrue(violations
        .contains("must not be null', propertyPath=parts[0].fraction"));
  }
}

Is this a Bug in the Validator or is something wrong in the code?


#2

Hi,

So your mistake is that you have tried to override the default group sequence of Whole and it’s not what you want to do.

You need to define a specific group sequence:

@GroupSequence({ Default.class, ComplexValidations.class })
interface MyGroupSequence {
}

(and remove the annotation you put on the Whole class)

Then you need to call HV like this:

violations = validator.validate( whole, MyGroupSequence.class )

This way, you enforce the ordered execution and your test passes.


#3

Thanks, that works.

Just to make sure, that I understand why:

Overriding the default group sequence of Whole imposes an ordering just on the validations of Whole and not recursively on Part, while passing a custom group sequence in validator.validate imposes an ordering on the validations of all validated classes.
Is that right?


#4

Overriding the default group sequence says: if you try to validate the default group for this bean, you need to validate these groups in this particular order. It doesn’t say anything about cascading (or parameters of a method for instance).

What you want to do is executing the validation with a given group sequence. So that’s what you should do :).