0

Java: 20 Springboot: 3.0.1

@NotBlank(message = "userId id can not be blank.")
@NotEmpty(message = "userId id can not be empty.")
@UUID
@User
private String userId;

now in request, I am not passing userId, I am getting any of the 4 validation error, but I am expecting, it should fail in the first validation(@NotBlank) itself.

I tried using GroupSequence like

@GroupSequence({Blank.class, Null.class, Empty.class, Custom.class, UserRequest.class})
  @UserType
  class UserRequest {
      @NotEmpty(groups = Empty.class, message = "userId id can not be empty.")
      @NotBlank(groups = Blank.class, message = "userId id can not be blank.")
      @NotNull(groups = Null.class, message = "userId id can not be null.")
      @UUID(groups = UID.class)
      @User(groups = Custom.class)
      private String userId;
  }

Still randomly error message is coming, it should first give Blank error message, then Null, then empty, then UUID, then custom.

Prafulla Kumar Sahu
  • 9,321
  • 11
  • 68
  • 105
  • Will you have a minimal reproducible example? I tried GroupSequence actually work as expected. – samabcde Jul 09 '23 at 08:29
  • @samabcde the one I have mentioned in the example is simple I feel. I added 5 validations to userId 4 are predefined and one is custom, let's consider only 3 of them, NotBlank, NotEmpty, UUI and add group sequence, if that will work with GroupSequence, that will be enough for me. Can you share how you have done it? That will be great help. – Prafulla Kumar Sahu Jul 09 '23 at 10:23
  • I added a test, basically follow what you tried, not sure if I misunderstand anything. – samabcde Jul 09 '23 at 14:03

2 Answers2

0

This is out of Spring Boot's control. It may be possible for Hibernate to order the reported constraint violations to match the order of the annotations but I'm not sure if the compiler, class file format, and reflection APIs provide any guarantees about that ordering. Also, jakarta.validation.Validator.validate(T, Class<?>...) returns a Set<ConstraintViolation<T>> which further suggests that there may be no guarantees about the order of the constraint violations.

From GitHub issue section: https://github.com/spring-projects/spring-boot/issues/35927

Prafulla Kumar Sahu
  • 9,321
  • 11
  • 68
  • 105
0

@GroupSequence should work according to Defining group sequences

By default, constraints are evaluated in no particular order, regardless of which groups they belong to. In some situations, however, it is useful to control the order in which constraints are evaluated.

One thing to note is the ordering should be (IMO):
NotNull -> NotEmpty -> NotBlank -> UUID As NotEmpty already checked NotNull and NotBlank already checked NotEmpty.

Below test is using spring boot 3.0.1 running with openjdk 20

import jakarta.validation.GroupSequence;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.hibernate.validator.constraints.UUID;

// validation is applied following this order
@GroupSequence({Null.class, Empty.class, Blank.class, UID.class, UserRequest.class})
public class UserRequest {
    // not related to annotation order here
    @NotBlank(groups = Blank.class, message = "userId id can not be blank.")
    @NotEmpty(groups = Empty.class, message = "userId id can not be empty.")
    @NotNull(groups = Null.class, message = "userId id can not be null.")
    @UUID(groups = UID.class)
    private String userId;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Set;
import java.util.stream.Stream;

@SpringBootTest
public class UserRequestValidateTest {

    @Autowired
    private Validator validator;

    @ParameterizedTest
    @MethodSource("invalidUserIds")
    void testUserValidationOrder(String userId, String expectedMessage) {
        UserRequest userRequest = new UserRequest();
        userRequest.setUserId(userId);
        Set<ConstraintViolation<UserRequest>> violations = validator.validate(userRequest, Null.class);
        assertTrue(violations.size() == 1);
        assertEquals(expectedMessage, violations.iterator().next().getMessage());
    }

    @MethodSource
    public static Stream<Arguments> invalidUserIds() {
        return Stream.of(arguments(null, "userId id can not be null."),
                arguments("", "userId id can not be empty."),
                arguments(" ", "userId id can not be blank."),
                arguments("not uuid", "must be a valid UUID"));
    }

}
samabcde
  • 6,988
  • 2
  • 25
  • 41