I have a problem when validating a form with Spring Boot. I have already found the cause of the problem (and know a possible solution), but I would like to understand why it happens.
In the controller that the post method reaches I have:
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String processAddNewEvent(@Valid @ModelAttribute("newEvent") EventDto newEvent, @ModelAttribute("imageFile") MultipartFile imageFile, BindingResult result, HttpServletRequest request) {
if(result.hasErrors()) {
return "addEvent";
}
return "redirect:/events";
}
What I want is just to validate the newEvent object. If it has errors, I show the "addEvent" form again with error messages.
But in this method, I receive two parameters from the form: the newEvent object and a file (imageFile). When this is the case, as soon as this function is called I get a MethodArgumentNotValidException. However, if I remove the imageFile and only receive the newEvent object, then the exception doesn't get thrown and the validation works correctly.
That is, if I the controller looks like this, the validation works and I don´t have exception:
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String processAddNewEvent(@Valid @ModelAttribute("newEvent") EventDto newEvent, BindingResult result, HttpServletRequest request) {
if(result.hasErrors()) {
return "addEvent";
}
return "redirect:/events";
}
So, my questions are, Can validation be done on one object and not the other? Shouldn't I receive two different objects from the form? What would be the correct way to do so?
Thanks and sorry for my English!
My idea to solve it is to put the image file inside the newEvent class, and thus receive only one object. I still don't know if it will work. I just want to understand the problem well
your issue arises because Spring tries to validate both objects (newEvent
and imageFile
) due to the presence of @Valid
on newEvent
, but only newEvent
is eligible for validation. MultipartFile
isn't meant to be validated with the standard bean validation mechanism (like @Valid
or @NotNull
), and thus causes the MethodArgumentNotValidException
.
Can validation be done on one object and not the other? Yes, validation can be applied only to the newEvent object. You are correctly using @Valid
on newEvent
, which triggers the validation of that specific object. MultipartFile
, however, should be handled separately, without applying validation annotations.
Shouldn't I receive two different objects from the form? Yes, you can receive two separate objects: newEvent
(the DTO) and MultipartFile
(the file). It's perfectly fine to handle them separately as long as you're not trying to validate the MultipartFile
object in the same way as newEvent
.
Solution
change the order of the parameters of method processAddNewEvent
, like this:
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String processAddNewEvent(
@Valid @ModelAttribute("newEvent") EventDto newEvent,
BindingResult result,
@ModelAttribute("imageFile") MultipartFile imageFile,
HttpServletRequest request) {
if (result.hasErrors()) {
return "addEvent"; // Return the form view with error messages
}
// Handle the imageFile separately (e.g., save the file, validate its size/type)
// Validate the imageFile
if (!imageFile.isEmpty()) {
// Example: Check the file size (e.g., max 2MB)
long maxFileSize = 2 * 1024 * 1024; // 2 MB
if (imageFile.getSize() > maxFileSize) {
result.rejectValue("imageFile", "error.imageFile", "File size exceeds the 2MB limit.");
return "addEvent";
}
// Example: Check the file type (e.g., only accept JPEG and PNG)
String contentType = imageFile.getContentType();
if (!"image/jpeg".equals(contentType) && !"image/png".equals(contentType)) {
result.rejectValue("imageFile", "error.imageFile", "Only JPEG and PNG files are accepted.");
return "addEvent";
}
// Save the image file to a specific directory
try {
String uploadDirectory = "/path/to/upload/directory"; // Replace with actual path
String fileName = imageFile.getOriginalFilename();
Path path = Paths.get(uploadDirectory + File.separator + fileName);
Files.copy(imageFile.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
result.rejectValue("imageFile", "error.imageFile", "Failed to save the file. Please try again.");
return "addEvent";
}
}
return "redirect:/events";
}