Search code examples
javaspringspring-bootbean-validationspring-restcontroller

Spring boot 2 rest validator called twice when groups are used


When I enable the @Validated spring annotation at class level in a Rest controller, 2 validation contexts are generated (each one with a different prefix). The @Validated annotation is required as I want to use 2 different validation groups but as there are 2 validations context all validations are executed twice. I think all validations should be verified only once so:

  • Is there any problem with my configuration?
  • Have I missed something?
    @RestController
    @RequestMapping
    @Validated
    public class KidController {
        private final KidService kidService;

        public KidController(KidService kidService) {
            this.kidService = kidService;
        }

        @PostMapping
        @Validated(OnRegistrationRequest.class)
        @ResponseStatus(HttpStatus.CREATED)
        public KidDTO createKidRegistration(@RequestBody @Valid KidDTO kid) {
            return kidService.saveKid(kid);
        }
    }

    public interface KidValidatorGroup {
        interface OnRegistrationRequest extends Default {
        }

        interface OnCheckInPersist extends Default {
        }
    }


    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @JsonPropertyOrder({"id"})
    @JsonInclude(Include.NON_NULL)
    public class KidDTO {
        @NotEmpty
        private String id;
        @NotEmpty
        private String firstName;
        @KidAgeConstraint
        private Integer age;
        @NotEmpty(groups = OnCheckInPersist.class)
        private String roomDocId;
        @Valid
        private Set<AnswerDTO> answers;
        @Valid
        private Set<GuardianDTO> guardians;
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @FieldStrictDependentConstraint(principalField = "answer", 
       dependentField = "description")
    @JsonInclude(Include.NON_NULL)
    public class AnswerDTO {
        @QuestionIdConstraint(groups = OnRegistrationRequest.class)
        private String questionId;
    }

I found the 2 contexts when debugging the application and the second one looks with method name like prefix.

Context Image 1 Context Image 2


Solution

  • In your code you have an annotation that triggers validation both on method parameter and on method return value:

        @PostMapping
        @Validated(OnRegistrationRequest.class) // this one is "@Valid" with a specified group and will be used for return value validation
        @ResponseStatus(HttpStatus.CREATED)
        public KidDTO createKidRegistration(@RequestBody @Valid /* this one is for method parameter */ KidDTO kid) {
            return kidService.saveKid(kid);
        }
    

    See the different paths that you have in your screenshots. One is createKidRegistration.kid.firstName that's the one for parameter. If you only need to validate the passed parameter with s specific group you should try to replace your @Valid with @Validated(OnRegistrationRequest.class), and remove the @Validated from the method level completely.