I'm uploading multiple files. This works fine when I actually select files to upload; they get into their List just fine, but when I click Upload without selecting a file, instead of getting an empty List, I get a List with an empty file in it. My validator is checking for the size of this List being 0, so it sneaks past it.
Yes, I can work around it and just check for an empty file, and yes I can disable the upload button when no file is selected, but I want to know why Spring does this and possibly how to stop it from doing it.
Here's the jsp:
<form:form commandName="documentsBean" enctype="multipart/form-data">
<input type="hidden" name="submittedFormAction" value="attachDocumentSave"/>
<input type="file" name="files" id="attachFiles" multiple/>
<button type="submit">Attach</button>
</form:form>
The bean, nothing fancy here:
public class DocumentsBean
{
private List<MultipartFile> files;
public List<MultipartFile> getFiles(){
return files;
}
public void setFiles(List<MultipartFile> files){
this.files = files;
}
}
The controller:
@RequestMapping( method = RequestMethod.POST, params = { "submittedFormAction=attachDocumentSave" })
public ModelAndView attachDocumentSave(HttpServletRequest request, @ModelAttribute("documentsBean") DocumentsBean documentsBean, BindingResult errors) throws Exception
{
// At this point documentsBean.files is an ArrayList<E> with size = 1
}
When I drill down into documentsBean.files
, I see elementData[0]
is a CommonsMultipartFile
, its fileName is an empty string and its size is 0.
What is going on?
Here is your solution. The explanation is below.
Add an @InitBinder
method that sets the following property. Ideally you would add it to a @ControllerAdvice
class, but the controller with the corresponding @RequestMapping
method works as well.
@InitBinder
public void init(WebDataBinder binder) {
binder.setBindEmptyMultipartFiles(false);
}
You'll also need to change your DocumentsBean
class like so
private List<MultipartFile> files = Arrays.asList();
So that the field isn't null
.
Spring uses a ServletModelAttributeMethodProcessor
to generate an argument for a @ModelAttribute
annotated parameter like yours
@ModelAttribute("documentsBean") DocumentsBean documentsBean
This HandlerMethodArgumentResolver
uses the request (and its form parameters) to generate an instance of whatever type your parameter is, by binding parameters to the instance class'
instance fields. If it detects that the request has a content type of multipart/form-data
, it will perform special binding.
This binding is done in WebDataBinder#bindMultipart(..)
. It retrieves all the MultipartFile
objects created by the MulitpartResolver
. If there is only one, it checks two things.
if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
mpvs.add(key, value);
}
The first method call
Return[s] whether to bind empty
MultipartFile
parameters.
and the second checks if the MultipartFile
has any body content or not. It won't if you submitted the form without selecting a file (or if you selected an empty file AFAIK).
If either of those expressions resolve to true
, then Spring will provide your handler method with this empty MultipartFile
holder. Otherwise, it will not bind that field, which is why you want a default value (so it doesn't remain null
)
private List<MultipartFile> files = Arrays.asList();
Take a look at the javadoc of WebDataBinder#setBindEmptyMultipartFiles(boolean)
which states
Set whether to bind empty MultipartFile parameters. Default is "true".
Turn this off if you want to keep an already bound MultipartFile when the user resubmits the form without choosing a different file. Else, the already bound MultipartFile will be replaced by an empty MultipartFile holder.
This is the property you should set to false
in the @InitBinder
method shown above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With