Spring MVC File Upload Example + Validator
In this tutorial we show how to upload a file using Spring MVC and apache commons-fileupload. The uploaded file is validated against a custom Spring Validator. When the file exceeds the maximum allowed upload size, we correctly handle the exception by catching the exception and adding an appropriate message to the response. We’ll show how to upload a single file and also how to upload multiple files.
Maven Dependencies
We need to add the following dependencies to the pom.xml
file, so maven can download and manage them automatically. Note, we added the commons-fileupload
which is used to upload a MultipartFile
. We also include the javax.validation
API so we can annotate our controller methods with the @Valid
annotation.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.memorynotfound.spring.mvc</groupId>
<artifactId>file-upload</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>SPRING-MVC - ${project.artifactId}</name>
<url>https://memorynotfound.com</url>
<packaging>war</packaging>
<properties>
<encoding>UTF-8</encoding>
<spring.version>4.2.6.RELEASE</spring.version>
</properties>
<dependencies>
<!-- spring dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- apache file upload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- java bean validation -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- servlet api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
Project Structure
Make sure your project looks similar to the following structure.
src
|--main
| +--java
| +--com
| +--memorynotfound
| +--config
| |--ServletInitializer.java
| |--WebConfig.java
| +--controller
| |--HomeController.java
| +--exception
| |--GlobalExceptionHandler.java
| +--model
| |--FileBucket.java
| |--MultiFileBucket.java
| +--validator
| |--FileValidator.java
| |--MultiFileValidator.java
| +--resources
| |--messages.properties
| +--webapp
| +--WEB-INF
| +--views
| |--multiple.jsp
| |--single.jsp
| |--success.jsp
pom.xml
Spring MVC File Upload Java/XML Configuration
The configuration for single or multiple file uploads is the same. The CommonsMultipartResolver
saves temporary files to the servlet container’s temporary directory. You can optionally configure the following properties: maxUploadSize, maxUploadSizePerFile, maxInMemorySize, uploadDir, defaultEncoding and resolveLazily. The last one ‘resolveLazily‘ is important for handling thrown exceptions correctly, we need to set this property to true.
package com.memorynotfound.config;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@EnableWebMvc
@Configuration
@ComponentScan({"com.memorynotfound"})
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
@Bean(name="multipartResolver")
public CommonsMultipartResolver multipartResolver(){
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSizePerFile(10240); //10Kb
resolver.setDefaultEncoding("UTF-8");
resolver.setResolveLazily(true);
return resolver;
}
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Here, we have the equivalent Spring XML File Upload configuration.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd ">
<mvc:annotation-driven />
<context:component-scan base-package="com.memorynotfound" />
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSizePerFile" value="10240"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="resolveLazily" value="true"/>
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
Handling Simple navigation
For simplicity, we put all the navigation cases in a single NavigationController
. Things to notice, we initialize the multiple upload page with 3 FileBucket
objects.
package com.memorynotfound.controller;
import com.memorynotfound.model.MultiFileBucket;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class NavigationController {
@RequestMapping(value = "/single", method = RequestMethod.GET)
public String single(Model model){
model.addAttribute("fileBucket", new FileBucket());
return "single";
}
@RequestMapping(value = "/multiple", method = RequestMethod.GET)
public String multiple(Model model){
model.addAttribute("multiFileBucket", new MultiFileBucket(3));
return "multiple";
}
@RequestMapping(value = "/", method = RequestMethod.GET)
public String index(){
return "index";
}
@RequestMapping(value = "/success", method = RequestMethod.GET)
public String success(){
return "success";
}
@RequestMapping(value = "/error", method = RequestMethod.GET)
public String error(){
return "error";
}
}
Validator Messages
These validation messages are used to display an appropriate message to the user. Add the messages.properties to the src/main/resources/ folder.
file.empty = Please select a file.
file.maxsize = {0} exceeds the maximum size of {1} Kb.
Spring MVC Single File Upload
The FileBucket
has a MultipartFile
object which will hold the uploaded file.
package com.memorynotfound.model;
import org.springframework.web.multipart.MultipartFile;
public class FileBucket {
MultipartFile file;
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file = file;
}
}
The SingleFileUploadController
is responsible for uploading a file to a /tmp/ directory. We inject a FileValidator
, which we’ll create later. We use the @InitBinder
annotation and provide a WebDataBinder
as an argument in the initBinderFileBucket
method to bind the FileValidator
onto the controller. Finally, we create the controller method which will handle the file upload. By annotating the FileBucket
with the @Valid
annotation, the binded validators are automatically executed on form submission. We use the BindingResult
to redirect to the current page if any errors have occurred. The RedirectAttributes
is used to forward the response to a success page. With the addFlashAttribute()
method, the attributes will survive a redirect.
package com.memorynotfound.controller;
import com.memorynotfound.model.FileBucket;
import com.memorynotfound.validation.FileValidator;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@Controller
@RequestMapping("/single")
public class SingleFileUploadController {
@Autowired
private FileValidator fileValidator;
@InitBinder
protected void initBinderFileBucket(WebDataBinder binder) {
binder.setValidator(fileValidator);
}
@RequestMapping(method = RequestMethod.POST)
public String handleFormUpload(@Valid FileBucket bucket,
BindingResult result,
RedirectAttributes redirectMap) throws IOException {
if (result.hasErrors()){
return "single";
}
MultipartFile file = bucket.getFile();
InputStream in = file.getInputStream();
File destination = new File("/some-location/" + file.getOriginalFilename());
FileUtils.copyInputStreamToFile(in, destination);
redirectMap.addFlashAttribute("filename", file.getOriginalFilename());
return "redirect:success";
}
}
This validator is used to validate if the uploaded file is not empty. If the file is empty, we reject the value and provide an appropriate error message.
package com.memorynotfound.validator;
import com.memorynotfound.model.FileBucket;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@Component
public class FileValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return FileBucket.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
FileBucket bucket = (FileBucket) target;
if (bucket.getFile() != null && bucket.getFile().isEmpty()){
errors.rejectValue("file", "file.empty");
}
}
}
To illustrate the Spring MVC File Upload example, we created a simple view to upload a single file. When the form is submitted, the method of the SingleFileUploadController
is executed on an HTTP POST method.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Spring MVC Upload Single File</title>
<style>
.error {
color: red;
}
</style>
</head>
<body>
<h1>Spring MVC Upload Single File</h1>
<form:form method="post" modelAttribute="fileBucket" enctype="multipart/form-data">
Upload File: <form:input type="file" path="file" id="file"/>
<br/>
<form:errors path="file" cssClass="error"/>
<br/><br/>
<input type="submit" value="Upload">
</form:form>
<a href="<c:url value='/'/>">Home</a>
</body>
</html>
Spring MVC Multi File Upload
The multiFileBucket
is used to upload multiple files. We reuse the previous FileBucket
. The constructor takes an integer
value, this value is used to create the number of upload elements.
package com.memorynotfound.model;
import java.util.ArrayList;
import java.util.List;
public class MultiFileBucket {
List<FileBucket> files = new ArrayList<FileBucket>();
public MultiFileBucket() {
}
public MultiFileBucket(int count){
for (int i = 0; i < count; i++){
files.add(new FileBucket());
}
}
public List<FileBucket> getFiles() {
return files;
}
public void setFiles(List<FileBucket> files) {
this.files = files;
}
}
The MultiFileUploadController
handles multiple file uploads. The only difference with the previous controller is that it uses a different Validator
e.g. the MultiFileValidator
, which we’ll create later on. We also use the new MultiFileBucket
which holds multiple files.
package com.memorynotfound.controller;
import com.memorynotfound.model.FileBucket;
import com.memorynotfound.model.MultiFileBucket;
import com.memorynotfound.validation.MultipleFileValidator;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@Controller
@RequestMapping("/multiple")
public class MultiFileUploadController {
@Autowired
private MultipleFileValidator multipleFileValidator;
@ModelAttribute
public MultiFileBucket multiFileBucket(){
return new MultiFileBucket(3);
}
@InitBinder
protected void initBinderFileBucket(WebDataBinder binder) {
binder.setValidator(multipleFileValidator);
}
@RequestMapping(method = RequestMethod.POST)
public String handleFormUpload(@Valid MultiFileBucket buckets,
BindingResult result,
RedirectAttributes redirectMap) throws IOException {
if (result.hasErrors()){
return "multiple";
}
String[] files = new String[buckets.getFiles().size()];
int index = 0;
for (FileBucket bucket : buckets.getFiles()){
MultipartFile file = bucket.getFile();
InputStream in = file.getInputStream();
File destination = new File("/some-location/" + file.getOriginalFilename());
FileUtils.copyInputStreamToFile(in, destination);
files[index] = file.getOriginalFilename();
index++;
}
redirectMap.addFlashAttribute("filenames", files);
return "redirect:success";
}
}
This validator validates multiple files. If any of the submitted files are empty, it’ll reject the request and return appropriate error messages for all the files.
package com.memorynotfound.validator;
import com.memorynotfound.model.FileBucket;
import com.memorynotfound.model.MultiFileBucket;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@Component
public class MultipleFileValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return MultiFileBucket.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
MultiFileBucket multiBucket = (MultiFileBucket) target;
int index = 0;
for (FileBucket bucket : multiBucket.getFiles()){
if (bucket.getFile() != null && bucket.getFile().isEmpty()){
errors.rejectValue("files[" + index + "].file", "file.empty");
}
index++;
}
}
}
To illustrate the Spring MVC File Upload for multiple files, we created the following view. The amount of files is managed by the MultiFileBucket
we initialized in the NavigationController
.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Spring MVC Upload Multiple Files</title>
<style>
.error {
color: red;
}
</style>
</head>
<body>
<h1>Spring MVC Upload Multiple Files</h1>
<form:form method="post" modelAttribute="multiFileBucket" enctype="multipart/form-data">
<c:forEach var="f" varStatus="fi" items="${multiFileBucket.files}">
<c:set var="file" value="files[${fi.index}].file"/>
Upload file: <form:input type="file" path="${file}" id="${file}"/>
<form:errors path="${file}" cssClass="error"/>
<br/>
</c:forEach>
<br/>
<input type="submit" value="upload"/>
</form:form>
<a href="<c:url value='/'/>">Home</a>
</body>
</html>
Spring MVC File Upload Exception Handling
The servlet container can throw multiple instances of the MultipartException
. This exception is thrown before it reaches the controller, so we need to add a global exception handler. We register this exception handler by annotating the class with the @ControllerAdvice
annotation. To handle specific exceptions we annotate the methods using the @ExceptionHandler
annotation and provide the exception type that the method must handle. In the method, we inspect the thrown exception and provide an appropriate message to the user.
package com.memorynotfound.exception;
import org.apache.commons.fileupload.FileUploadBase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@ControllerAdvice
public class GlobalExceptionHandler {
@Autowired
private MessageSource messageSource;
@ExceptionHandler(value = MultipartException.class)
public RedirectView handleMultipartException(Exception ex, HttpServletRequest request){
RedirectView model = new RedirectView("error");
FlashMap flash = RequestContextUtils.getOutputFlashMap(request);
if (ex instanceof MultipartException) {
MultipartException mEx = (MultipartException)ex;
if (ex.getCause() instanceof FileUploadBase.FileSizeLimitExceededException){
FileUploadBase.FileSizeLimitExceededException flEx = (FileUploadBase.FileSizeLimitExceededException)mEx.getCause();
float permittedSize = flEx.getPermittedSize() / 1024;
String message = messageSource.getMessage(
"file.maxsize",
new Object[]{flEx.getFileName(), permittedSize},
LocaleContextHolder.getLocale());
flash.put("errors", message);
} else {
flash.put("errors", "Please contact your administrator: " + ex.getMessage());
}
} else {
flash.put("errors", "Please contact your administrator: " + ex.getMessage());
}
return model;
}
@ExceptionHandler(value = IOException.class)
public RedirectView handleIOException(Exception ex, HttpServletRequest request){
RedirectView model = new RedirectView("error");
FlashMap flash = RequestContextUtils.getOutputFlashMap(request);
flash.put("errors", "Please contact your administrator: " + ex.getMessage());
return model;
}
}
Success View
When the file is successfully uploaded, we display the name of the file.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Spring MVC File Upload Success</title>
</head>
<body>
<h1>Spring MVC File Upload Success</h1>
<c:if test="${not empty filename}">
${filename} uploaded successfully.
<br/><br/>
</c:if>
<c:if test="${not empty filenames}">
<c:forEach var="file" items="${filenames}">
${file} uploaded successfully.
<br/>
</c:forEach>
<br/><br/>
</c:if>
<a href="<c:url value='/'/>">Home</a>
</body>
</html>
Error View
If an error occurs, we display the content of the error.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Spring MVC File Upload Error</title>
<style>
.error {
color: red;
}
</style>
</head>
<body>
<h1>Spring MVC File Upload Error</h1>
<span class="error">${errors}</span>
<br/><br/>
<a href="<c:url value='/'/>">Home</a>
</body>
</html>
Demo
Single file upload.
Multiple file uploads.
Error page.
Success page.
References
- Spring Multipart Upload Documentation
- CommonsMultipartResolver JavaDoc
- MultipartFile JavaDoc
- @ControllerAdvice JavaDoc
- @ExceptionHandler JavaDoc
Very helpful, thanks a lot for posting this.
What would it look like in case of a REST service though? Would we need a custom serializer for FileBucket/MultiFileBucket to handle the JSON translation?