spring2019년 1월 2일10 min read

Handling File Uploads in Spring

How to implement single, multiple, and metadata-included file uploads in Spring.

FFrank Advenoh
#web#spring#file

1. Introduction

In this post, let's look at how to implement file uploads in Spring. Spring provides file upload functionality in several ways, not just single file uploads, as follows.

  • Single file upload
  • Multiple file upload
  • File upload + additional information individually by @RequestParam
  • File upload + additional information mapped to a class all at once by @ModelAttribute

Spring supports file uploads with the MultipartResolver interface and the following two implementations.

  • Using Servlet 3.0 Multipart Request
    • Implementation : StandardServletMultipartResolver
    • You only need to configure it (XML, JavaConfig).
  • Using the Apache Commons FileUpload API
    • Implementation : CommonsMultipartResolver
    • It is not limited to the Servlet 3 environment but works the same in a Servlet 3.x container as well * You need to add the library to pom.xml

2. Development Environment

3. Configuration for File Upload

Let's look at the basic configuration needed in Spring for file uploads. As mentioned, Spring can configure file uploads in two ways. In this post, let's explain mainly the first resolver, StandardServletMultipartResolver. You can also use the second resolver, CommonsMultipartResolver. If you have experience configuring Spring, I think you won't have much difficulty whichever you use. For the necessary content, please refer to the reference links.

  • Servlet 3.0 Multipart Request (explained mainly with this method)
    • standardServletMultipartResolver
  • Apache Commons FileUpload API
    • CommonsMultipartResolver

3.1 Spring and File Upload Limit Configuration

3.1.1 Configuration When Using StandardServletMultipartResolver

Bean XML definition file configuration 1. Register the StandardServletMultipartResolver bean in Spring

File : spring-mvc-xml-fileupload_src_main_webapp_WEB-INF/spring-mvc-config.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />

2. Declare multipart-config in the servlet configuration

File : spring-mvc-xml-fileupload_src_main_webapp_WEB-INF/web.xml

<servlet>
    <servlet-name>hello-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>_WEB-INF_spring-mvc-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <multipart-config>
        <max-file-size>209715200</max-file-size>
        <max-request-size>209715200</max-request-size>
        <file-size-threshold>0</file-size-threshold>
    </multipart-config>
</servlet>

You can limit file uploads by configuring various options in Multipart-config.

  • location - the absolute path where files are temporarily stored during upload
    • Default value : "
  • maxFileSize - the maximum file size per file
    • Default value : no limit
  • maxRequestSize - not the size of a single file, but the maximum file size per multipart/form-data request (think of it as the total size when uploading multiple files) * Default value: no limit
  • fileSizeThreshold - represents the size limit at which an uploaded file is passed directly as a stream from memory rather than being temporarily stored as a file
    • Default value: 0
    • ex. if you set 1024 * 1024 = 1MB, the file is stored as a temporary file only when it is 1MB or larger

JavaConfig Configuration

1. Register the StandardServletMultipartResolver bean in Spring

File : spring-mvc-javaconfig-fileupload-form/src/main/java/com/boraji/tutorial/spring/config/WebConfig.java
  
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.boraji.tutorial.spring.controller" })
public class WebConfig extends WebMvcConfigurerAdapter {
(omitted)...

    @Bean
    public MultipartResolver multipartResolver() {
        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        return multipartResolver;
    }
}

2. Register the Multipart-config settings as a MultipartConfigElement object

File : spring-mvc-javaconfig-fileupload-form/src/main/java/com/boraji/tutorial/spring/config/MyWebAppInitializer.java
  
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    private static int MAX_FILE_ZIZE = 10 * 1024 * 1024;
(omitted)...
    @Override
    protected void customizeRegistration(Dynamic registration) {
        MultipartConfigElement multipartConfig = new MultipartConfigElement("tmp_upload", MAX_FILE_ZIZE, MAX_FILE_ZIZE, 0);
        registration.setMultipartConfig(multipartConfig);
    }
}

2.1 (Another method) Configuring file upload limits with the @MultipartConfig annotation You can create a custom servlet and configure multipart-config with the @MultipartConfig annotation. Let's not cover this configuration in detail. For additional explanation, please refer to the @MultipartConfig❲Servlet 3.x❳ blog.

@MultipartConfig(location=/tmp,
fileSizeThreshold=0,
maxFileSize=5242880, //5 MB
maxRequestSize=20971520) //20 MB
public class FileUploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //handle file upload
}

3.1.2 Configuration When Using CommonsMultipartResolver

Unlike StandardServletMultipartResolver, when using the CommonsMultipartResolver resolver, you need to additionally add the commons-fileupload library to the pom.xml file.

Adding the Dependency

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

The Spring configuration can be done with a Bean definition file or JavaConfig.

Bean XML Definition File Configuration

Register the CommonsMultipartResolver bean in the Spring configuration.

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/>
<property name=“maxUploadSizePerFile" value="10485760”/>
<property name="maxInMemorySize" value=“0"/>
</bean>

JavaConfig Configuration

File : spring-mvc-javaconfig-fileupload-ajax/src/main/java/com/boraji/tutorial/spring/config/WebConfig.java
  
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.boraji.tutorial.spring.controller" })
public class WebConfig extends WebMvcConfigurerAdapter {
    private final int MAX*SIZE = 10 * 1024 \- 1024;
(omitted)...
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setMaxUploadSize(MAX_SIZE); 10MB
        multipartResolver.setMaxUploadSizePerFile(MAX_SIZE); 10MB
        multipartResolver.setMaxInMemorySize(0);
        return multipartResolver;
    }
}

4. File Upload Examples

So far we have covered the Spring-related configuration. Now let's look at how to upload files in the view and controller layers. There are several ways to upload files. Let's also check the differences through examples.

4.1 Single File Upload

This is an example of uploading a single file. The view is written with the HTML input tag's file attribute so that you can upload a file via a form. The form tag's enctype attribute is set to multipart/form-data so that the browser operates in file upload mode.

List of enctype attribute values

ValueDescription
multipart/form-dataUsed for file uploads (no encoding)
application_x-www-form_urlencodedThe default value, encodes all characters
text/plainSpaces are converted to the + sign. Special characters are not encoded
<h3>Single File Upload</h3>
<form action="singleFileUpload" method="post" **enctype**="multipart/form-data">
  <table>
    <tr>
      <td>Select File</td>
      <td><input type="**file**" name="**mediaFile**" /></td>
      <td>
        <button type="submit">Upload</button>
      </td>
    </tr>
  </table>
</form>

View screen in the browser

In the controller, the uploaded file is received using a MultipartFile variable. The MultipartFile class provides information about the file (file name, size, etc.) and file-related methods (e.g. saving the file). The representative methods used are as follows; for more details, please refer to the API.

  • transferTo() : saves the file
  • getOriginalFilename() : returns the file name as a String value
  • getSize() : returns the file size
  • getInputStream() : obtains the input stream for the file
@PostMapping("/singleFileUpload")
public String singleFileUpload(@RequestParam("mediaFile") **MultipartFile** file, Model model)
throws IOException {

    // Save mediaFile on system
    if (!file.getOriginalFilename().isEmpty()) {
        file.transferTo (new File(DOWNLOAD_PATH + "/" + SINGLE_FILE_UPLOAD_PATH, file.getOriginalFilename()));
        model.addAttribute("msg", "File uploaded successfully.");
    } else {
        model.addAttribute("msg", "Please select a valid mediaFile..");
    }
    return "fileUploadForm";
}

Since the file name passed as a request parameter is passed as mediaFile, it is specified with the @RequestParam(“mediaFile”) annotation. And if the file is not empty, it is saved to the designated download path, and the result message is passed to the fileUploadForm view through the Model class.

Bonus Tips When implementing a REST API, JSON is widely used as the data format exchanged between client and server. When exchanging in JSON type, it is encoded by default before sending. If the data being sent is small, it's not a problem, but in the case of large JSON, it greatly affects performance. In such cases, if you send the JSON data as a stream and receive it as a MultipartFile in the controller, performance improves a lot because it is simply received in stream form. It's a part worth considering when working on a project.

4.2 Multiple File Upload

The way to upload multiple files is very similar to the single file upload example. The difference is that you need to add the multiple attribute in the view so that the user can select multiple files.

<h3>Multiple File Upload</h3>
<form action="multipleFileUpload" method="post" enctype="multipart/form-data">
  <table>
    <tr>
      <td>Select Files</td>
      <td><input type="file" name="mediaFile" **multiple="multiple" ** /></td>
      <td>
        <button type="submit">Upload</button>
      </td>
    </tr>
  </table>
</form>

View screen in the browser

In the controller, the MultipartFile variable is declared as an array so that it can receive multiple files. Looping through the array, each file is saved to the designated path. You can save it with the transferTo method provided by the MultipartFile class, but you can also save the file directly with the OutputStream class.

@PostMapping("/multipleFileUpload")
public String multipleFileUpload(@RequestParam("mediaFile") **MultipartFile[]** files,
Model model) throws IOException {

    Save mediaFile on system
    for (MultipartFile file : files) {
        if (!file.getOriginalFilename().isEmpty()) {
            BufferedOutputStream outputStream = new BufferedOutputStream(
            new FileOutputStream(
            new File(DOWNLOAD_PATH + "/" + MULTI_FILE_UPLOAD_PATH, file.getOriginalFilename())));

            outputStream.write(file.getBytes());
            outputStream.flush();
            outputStream.close();
        } else {
            model.addAttribute("msg", "Please select at least one mediaFile..");
            return "fileUploadForm";
        }
    }
    model.addAttribute("msg", "Multiple files uploaded successfully.");
    return "fileUploadForm";
}

4.3 File Upload + Additional Information by @RequestParam

This example sends a file together with other input information. Modify the form so that it can receive additional input.

<h3>File Upload + Additional Information by @RequestParam</h3>
<form
  action="singleFileUploadWithAdditionalData"
  method="post"
  enctype="multipart/form-data"
>
  Creator:<br />
  <input type="text" **name="creator" ** />
  <br />
  CallbackUrl:<br />
  <input type="text" **name="callbackUrl”** >
  <br />
  <input type="file" **name="mediaFile" ** />
  <br /><br />
  <button type="submit">Upload</button>
</form>

View screen in the browser

In the controller, the @RequestParam annotation is declared on each variable so that input data can be received individually.

@PostMapping("/singleFileUploadWithAdditionalData")
public String singleFileUploadWith(@RequestParam("mediaFile") MultipartFile **file**,
@RequestParam final String **creator**, @RequestParam final String **callbackUrl**, Model model) throws IOException {

  log.info("creator : {}", creator);
  log.info("callbackUrl : {}", callbackUrl);

  // Save mediaFile on system
  if (!file.getOriginalFilename().isEmpty()) {
    file.transferTo(new File(DOWNLOAD_PATH + "/" + SINGLE_FILE_UPLOAD_AND_EXTRA_DATA1_PATH, file.getOriginalFilename()));
    model.addAttribute("msg", "File uploaded successfully.");
  } else {
    model.addAttribute("msg", "Please select a valid mediaFile..");
  }
  return "fileUploadForm";
}

4.4 File Upload + Additional Information by @ModelAttribute

In the 4.3 example, the input data was stored in individual variables. However, when there is a lot of input data, there is a disadvantage that the number of method arguments increases a lot. Using the @ModelAttribute annotation, you can map the input data to a class all at once.

For class mapping, create a class that includes variables with the same names as the data entered in the Form, as follows.

@Data
public class MediaVO {
  private String creator;
  private String callbackUrl;
  private MultipartFile mediaFile;
}

Declare the class MediaVO, which will receive the user input data, as an argument.

@RequestMapping(value = "/uploadFileModelAttribute", method = RequestMethod.POST)
public String singleFileUploadWith( **@ModelAttribute MediaVO mediaVO**, Model model) throws IOException {
  MultipartFile file = mediaVO.getMediaFile();

  log.info("mediaVO: {}", mediaVO);

  // Save mediaFile on system
  if (!file.getOriginalFilename().isEmpty()) {
    file.transferTo(new File(DOWNLOAD_PATH + "/" + SINGLE_FILE_UPLOAD_AND_EXTRA_DATA2_PATH, file.getOriginalFilename()));

  model.addAttribute("msg", "File uploaded successfully.");
  } else {
    model.addAttribute("msg", "Please select a valid mediaFile..");
  }
  return "fileUploadForm";
}

Since the uploaded file and input data are included in MediaVO, you fetch the desired data with getters and use it in the logic.

So far we have looked at file uploads using Spring. Next time, let's look at Spring controller exception handling.

5. References

관련 글