If i use @RequestBody, everything works fine. However, i cannot use @RequestBody when sending files.
@PostMapping
public ResponseEntity<?> postProduct(@Valid PostProduct postProduct, BindingResult bindingResult) {
        if(bindingResult.hasErrors()) {
            for (ObjectError objectError : bindingResult.getAllErrors()) {
                System.out.println(objectError.toString());
            }
        }
        return ResponseEntity.ok().build();
    }
The error is still thrown but the handleMethodArgumentNotValid of ControllerAdvice is not invoked.
I don't understand this behavior.
UPDATE 1
Here's the PostProduct class
@Data
public class PostProduct {
    @NotBlank(message = "product name must not be blank")
    private String name;
    @NotEmpty
    @UniqueElements
    private List<@NotNull @Positive Integer> materials;
    @NotEmpty
    @UniqueElements
    private List<@NotNull @Positive Integer> colors;
    @NotNull
    @Positive
    private Integer price;
    @NotNull
    private List<@NotNull @ProductImageConstraint MultipartFile> images;
}
The ControllerAdvice:
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        System.out.println("handleMethodArgumentNotValid");
        Map<String, String> errors = new HashMap<>();
        List<ObjectError> objectErrors = ex.getBindingResult().getAllErrors();
        return returnError(objectErrors);
    }
public static ResponseEntity<Object> returnError(List<ObjectError> objectErrors) {
        Map<String, String> errors = new HashMap<>();
        objectErrors.forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return ResponseEntity.badRequest().body(errors);
    }
}
I use 1 custom validator, don't know it has anything to do with this strange behavior but i'll post it anyway.
@Documented
@Constraint(validatedBy = ProductImageValidator.class)
@Target( { FIELD, TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ProductImageConstraint {
    String message() default "not image or file empty";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
public class ProductImageValidator implements
        ConstraintValidator<ProductImageConstraint, MultipartFile> {
    @Override
    public void initialize(ProductImageConstraint constraintAnnotation) {
    }
    @Override
    public boolean isValid(MultipartFile image, ConstraintValidatorContext context) {
        boolean isValid = false;
        String contentType = image.getContentType();
        if (contentType != null && contentType.contains("image") && !image.isEmpty()) {
            isValid = true;
        }
        return isValid;
    }
}
