21

I have the following controller method for uploading multiple files at once, inspired by this blog post and answers to this question as well:

@RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
@PreAuthorize(...)
public void upload(@PathVariable User user, 
                   @RequestParam("file") List<MultipartFile> files) {
  // handle files
}

However, the list of the files is always empty although request contains them.

If I add the third MultipartRequest parameter to the method:

public void upload(@PathVariable User user, 
                   @RequestParam("file") List<MultipartFile> files,
                   MultipartRequest request)

I can see it contains my uploaded files correctly:

request contents

What might be the reason of empty List<MultipartFile>?

I'm using ng-file-upload to submit the files, but I don't think it is connected with the issue. Spring 4.2.4.

Community
  • 1
  • 1
fracz
  • 20,536
  • 18
  • 103
  • 149
  • Your config? You need a library and a matching resolver for multipart uploads. – a better oliver Feb 03 '16 at 08:25
  • I assumed you read the blog post you were referring to. `commons-fileupload` e.g. – a better oliver Feb 03 '16 at 08:35
  • Yes, I have all that set up. In fact, I can receive those files by reading them from `multipartRequest.getFileMap().values()`. What I'm asking is why I cannot simply use `List` as suggested in the sources I refer to. – fracz Feb 03 '16 at 08:38
  • Presumably something is missing. If, for instance, the standard servlet multipart support is included but you use the commons multipart resolver - as suggested in the blog post - `files` won't get populated. – a better oliver Feb 03 '16 at 08:51
  • Is it possible to get List with List in request? – Sindhu Arju Apr 07 '20 at 13:35

5 Answers5

23

The problem was that ng-file-upload by default submits array of files using names file[0], file[1] etc. It is configurable with the arrayKey value when using Upload Service. Setting it to empty string forces the files to be sent under the same file key, which is correctly resolved with Spring and the @RequestParam("file") List<MultipartFile> contains all files that has been submitted.

Upload.upload({url: url, data: {file: arrayOfFiles}, arrayKey: ''})
fracz
  • 20,536
  • 18
  • 103
  • 149
4

Try to use @ModelAttribute like this:

    @RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
    @PreAuthorize(...) 
    public void upload(@PathVariable User user,@ModelAttribute("uploadFile") FileUpload uploadFile) throws IllegalStateException, IOException {

    List<MultipartFile> files = uploadFile.getFiles();
    ...

And create a class like:

     public class FileUpload {
     private List<MultipartFile> files;
     public List<MultipartFile> getFiles() {
        return files;
     }

    public void setFiles(List<MultipartFile> files) {
       this.files= files;
      }
   }
Abdelhak
  • 8,299
  • 4
  • 22
  • 36
2

That works for me, sending big 'email' object with multiple file attachments from UI to back-end:

Angular

sendEmailWithAttachments(taskId: string, template: string, email: any, modelConfig: any, files: any[]) {
    let formData = new FormData();
    formData.append('form', new Blob([JSON.stringify(email)], {type: 'application/json'}));
    files.forEach(file  => {
        formData.append('files', file);
    });

    return this.$http({
        method: 'POST',
        data: formData,
        url: this.baseUrl + '/' + taskId + '/email-with-attachment?template=' + template,
        headers: {
            'Content-Type': undefined
        },
        responseType: 'arraybuffer'
    });
}

Java Spring

@RequestMapping(value = "{taskId}/email-with-attachment", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE)
public void sendEmailWithAttachment(
        @PathVariable String taskId,
        @RequestParam String template,
        @RequestParam("form") MultipartFile form,
        @RequestParam("files") List<MultipartFile> files) throws IOException {
    Map<String, String> parameters = new ObjectMapper().readValue(form.getInputStream(), HashMap.class);
    
    System.out.println("taskId"+ taskId);
    System.out.println("template"+ template);
    System.out.println("files"+ files);
    System.out.println("parameters"+ parameters);
}
Community
  • 1
  • 1
Dmitri Algazin
  • 3,332
  • 27
  • 30
2

for multiple files. do this in your javascript

//first add files to form data
var formData = new FormData();
for (let i = 0; i < files.length; i++) {
    formData.append("images", files[i]);
}

//post files to backend e.g using angular
 $http.post('upload', formData, {
     transformRequest: angular.identity,
     headers: {'Content-Type': undefined}
 })
 .then(function(response){
      console.log("UPLOAD COMPLETE::=> ", response);
 }, function (error) {
      console.log(error);
 });

Do this in your java

//your java method signature
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
public Response uploadImage(@RequestParam(value = "images") MultipartFile[] images){

}
Timetrax
  • 1,373
  • 13
  • 15
0

I think that in the way you sent data from front, it can not bound with java.util.List. If you create a JSON data as request and you annotated your List with @RequestBody like:

@RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
@PreAuthorize(...)
public void upload(@PathVariable User user, 
                   @RequestBody List<MultipartFile> files) {
  // handle files
}

this should work. Some info here.

fracz
  • 20,536
  • 18
  • 103
  • 149
alexandrum
  • 429
  • 9
  • 17
  • Didn't work out-the-box for me, but I prefer the approach. Ended up using @francz method above. – sparkyspider Jun 01 '16 at 14:09
  • The solution you propose with the @RequestBody is, in fact, correct when the body of the request has a direct translation into a domain object you have on the backend. For instance, it can be used to parse the body from JSON data to an object. However, it will not work with MultipartFile. – João Dias Amaro Oct 31 '16 at 14:41
  • Will i be able to get List and List ? – Sindhu Arju Apr 07 '20 at 13:33
  • Not with this format of API structure, but you can make a POJO with both lists and try to work with that. – alexandrum Apr 07 '20 at 13:44