@Valid on containers is deprecated but validation doesn't always work the same if moving it to the elements

Hello !

I’m trying to update our code to follow the deprecation of annotation @Valid at container level. It works well in most situations, but in one the validation changed behavior.

I have an interface EventsControllerApi for defining a Spring controller using WebFlux. One endpoint takes a list of DTO as an argument, and both the list and the elements of the list are currently annotated with @Valid. It’s implemented by a class EventsController annotated with @RestController. I have a test checking that if a required attribute of one element is missing in the payload, the endpoint returns a 400.

When I remove the @Valid on the list, the test fails because it now returns a 500. I found a way to generate again the 400 by adding @Validated on the controller interface and defining an ErrorHandler for ConstraintViolationException (before it was probably going through the handler for HttpMessageNotReadableException). Both modifications are needed.

Is there a reason for this change of behavior?

public interface EventsControllerApi {

  @Operation(summary = "Get events related to various resources.")
  @PostMapping(
      consumes = MediaType.APPLICATION_JSON_VALUE,
      produces = MediaType.APPLICATION_JSON_VALUE)
  List<ResourceDto> getEventsByResources(
      @PathVariable("organization-id") String organizationId,
      @RequestBody @Valid List<@NotNull @Valid ResourceIdRequestDto> resourceIds);
@Schema(description = "a uuid with context from its domain")
@Valid
public record ResourceIdRequestDto(
    @JsonProperty("domain")
        @NotBlank
        @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
        String domain,
    @JsonProperty("type")
        @NotBlank
        @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
        String type,
    @JsonProperty("id") @NotNull UUID id) {}
class EventsControllerConsistencyTest {

  @Autowired WebTestClient webTestClient;

  @Test
  void shouldReturn400WhenIdIsNull() {
    webTestClient
        .post()
        .uri("/organizations/{organizationId}/events", "organization")
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(
            """
            [
              {
                "domain": "domain",
                "type": "event"
              }
            ]
            """)
        .exchange()
        .expectStatus()
        .isBadRequest();
  }

Hey @NicolasV

Thanks for reaching out! I think that that’s a Spting integration issue more than the “where @Valid is located” … I remember at a time Spring was not able to validate the parameters of a collection type, as validation was going through a validator.validate(..) instead of validator.forExecutables().validateParameters(..).

See, for example, this test case on how things would’ve been translated to pure validator logic:

For example, this @Valid:

shouldn’t be needed either.