File upload using Java/Spring MVC

Posted on 13-02-2013 20:05 by graham
This tutorial shows how to upload a file onto your server using Spring MVC.

Components


The upload mechanism will consist of three main parts:
- an upload form on a JSP page
- a controller for the page that will handle the upload process
- a wrapper class for the uploaded item

The upload form


We start with building a page containing a form that will allow the user to browse for a file they want to upload, and then actually upload it. With Spring MVC's form:input tag of type file, this is much facilitated. The complete code for the form looks like this:
uploadForm.jsp
<%@ 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" pageEncoding="UTF-8" %>

<form:form enctype="multipart/form-data" modelAttribute="uploadItem" method="post" action="/image/upload">
<form:label for="fileData" path="fileData">File:</form:label>
<form:input path="fileData" type="file" />
<input type="submit" value="Upload" />
</form:form>

The interesting part is the modelAttribute attribute on the form. It points to a variable called uploadItem - this will be a property in the controller into which the uploaded file will be stored. It will be discussed below.

The form controller


Now it's time to move to the back-end part and build a controller for our form. The controller will contain two actions: one that is called before the upload form is displayed, the other one handles a submission of the upload form.

The complete code for the controller is:
FileUploadController.java
package com.wordgraphs.fileupload;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;

import javax.imageio.ImageIO;
import javax.inject.Inject;

import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ImageUploadController
{
private Integer IMAGE_MAX_SIZE = 500000;

private String uploadsDir = "/opt/tomcat/uploads";

// this method is prepares the upload form for display
@RequestMapping(value = "/image/showUploadForm", method = RequestMethod.GET)
public ModelAndView showUploadForm()
{
ModelAndView mv = new ModelAndView("image/uploadForm");

// prepare an item that will store the uploaded file
mv.addObject("uploadItem", new UploadItem());

return mv;
}

// this method will be called when the upload form is submitted
@RequestMapping(value = "/image/upload", method = RequestMethod.POST)
public ModelAndView upload (UploadItem uploadItem, HttpSession session) throws Exception
{
MultipartFile file = uploadItem.getFileData();

InputStream inputStream = file.getInputStream();
String fileName = uploadsDir + "/" + file.getOriginalFilename();

OutputStream outputStream = new FileOutputStream(fileName);

// write the file to disk
int readBytes = 0;
byte[] buffer = new byte[IMAGE_MAX_SIZE];
while ((readBytes = inputStream.read(buffer, 0, 10000)) != -1)
{
outputStream.write(buffer, 0, readBytes);
}
outputStream.close();
inputStream.close();

// return a success page
return new ModelAndView("redirect:/image/uploadSuccess");
}

private Image getImage(InputStream inputStream) throws IOException
{
BufferedImage bufferedImage;
bufferedImage = ImageIO.read(inputStream);
Image image = new Image();
image.setHeight(bufferedImage.getHeight());
image.setWidth(bufferedImage.getWidth());

return image;
}
}

The UploadItem wrapper


The only part we haven't discussed yet is the class that wraps the uploaded item. As you will see, it's nothing too fancy about it:
UploadItem.java
import org.springframework.web.multipart.commons.CommonsMultipartFile;

public class UploadItem
{
private String filename;
private CommonsMultipartFile fileData;

public String getFilename()
{
return filename;
}

public void setFilename(String filename)
{
this.filename = filename;
}

public CommonsMultipartFile getFileData()
{
return fileData;
}

public void setFileData(CommonsMultipartFile fileData)
{
this.fileData = fileData;
}
}


Multipart resolver


We also need to add the multipart resolver to the app context file:
app-context.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />


Adding validation


The controller class presented above was a really minimalistic case. In your real application, you will want to have some validation on the upload form, especially for:
- size limit on the uploaded file
- allowed file extensions
- preventing upload of empty files

Below is an extended version of our controller that handles all these cases:
FileUploadController.java
package com.wordgraphs.fileupload;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;

import javax.imageio.ImageIO;
import javax.inject.Inject;

import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ImageUploadController
{
private Integer IMAGE_MAX_SIZE = 500000;

// list of allowed file extensions
private Set<String> allowedImageExtensions;

// list of error messages
private List<String> errorMsgs = new ArrayList<String>();

private String uploadsDir = "/opt/tomcat/uploads";

public ImageUploadController()
{
// define allowed file extensions
this.allowedImageExtensions = new HashSet<String>();
this.allowedImageExtensions.add("png");
this.allowedImageExtensions.add("jpg");
this.allowedImageExtensions.add("gif");

// init error messages
clearMessages();
}

// this method is prepares the upload form for display
@RequestMapping(value = "/image/showUploadForm", method = RequestMethod.GET)
public ModelAndView showUploadForm()
{
clearMessages();
ModelAndView mv = new ModelAndView("image/uploadForm");

// prepare an item that will store the uploaded file
mv.addObject("uploadItem", new UploadItem());

return mv;
}

// this method will be called when the upload form is submitted
@RequestMapping(value = "/image/upload", method = RequestMethod.POST)
public ModelAndView upload (UploadItem uploadItem, HttpSession session) throws Exception
{
// the state of the controller is preserved between calls, so each time
// we need to clear the error messages from the previous submission
clearMessages();

try
{
if (file.getSize() > 0)
{
InputStream inputStream = file.getInputStream();

String extension = FilenameUtils.getExtension(file.getOriginalFilename());

if (!this.allowedImageExtensions.contains(extension))
{
ModelAndView mv = new ModelAndView("image/uploadForm");
addError("Incorrect file extension - only JPG, GIF, PNG i TIFF are allowed");
mv.addObject("errorMsgs", this.errorMsgs);
return mv;
}

if (file.getSize() > IMAGE_MAX_SIZE)
{
ModelAndView mv = new ModelAndView("image/uploadForm");
addError("File size too large");
mv.addObject("errorMsgs", this.errorMsgs);
return mv;
}

fileName = uploadsDir + "/" + file.getOriginalFilename();

OutputStream outputStream = new FileOutputStream(fileName);

int readBytes = 0;
byte[] buffer = new byte[IMAGE_MAX_SIZE];
while ((readBytes = inputStream.read(buffer, 0, 10000)) != -1)
{
outputStream.write(buffer, 0, readBytes);
}
outputStream.close();
inputStream.close();

return new ModelAndView("redirect:/image/uploadSuccess");
}
else
{
ModelAndView mv = new ModelAndView("image/uploadForm");
addError("The file is empty");
mv.addObject("errorMsgs",this.errorMsgs);
return mv;
}
}
catch (Exception e)
{
addError("Unknown error while uploading the file: " + e.getMessage());
ModelAndView mv = new ModelAndView("image/uploadForm");
mv.addObject("errorMsgs", this.errorMsgs);
return mv;
}
}

private Image getImage(InputStream inputStream) throws IOException
{
BufferedImage bufferedImage;
bufferedImage = ImageIO.read(inputStream);
Image image = new Image();
image.setHeight(bufferedImage.getHeight());
image.setWidth(bufferedImage.getWidth());

return image;
}

private void clearMessages()
{
this.errorMsgs = new ArrayList<String>();
}

public void addError(String msg)
{
this.errorMsgs.add(msg);
}
}

Displaying error messages on the upload form


What we have added is some validation. If any of the input data is incorrect, or if an error occurs, we store the error message into the errorMsgs list. The only thing we need to do now is to make the upload form display these messages:
uploadForm.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>

<c:if test="${fn:length(errorMsgs) > 0}">
<ul>
<c:forEach items="${errorMsgs}" var="error">
<li><c:out value="${error}" /></li>
</c:forEach>
</ul>
</c:if>

<form:form enctype="multipart/form-data" modelAttribute="uploadItem" method="post" action="/image/upload">
<form:label for="fileData" path="fileData">File:</form:label>
<form:input path="fileData" type="file" />
<input type="submit" value="Upload" />
</form:form>
Comments
Nice tutorial, thanks!

it would have been absolutely great if you provided a working example project (maybe a Maven or Gradle one to be able to manage the dependencies).

;-)
Added on 07-07-2013 16:20 by anonymous
very helpful. However I followed this and the errors never got displayed in the JSP. wondering what could be the problem?
Added on 18-10-2013 00:58 by anonymous

 

Add comment

Has this tutorial been helpful to you? Or do you see anything wrong? We appreciate your opinion!
Your comment:
Show formatting hints
HTML is disallowed, but in your text you can use the following markup
  • [code][/code] for a block of code
  • [tt][/tt] for inline code
  • [link]link href|link anchor[/link] for links
  • [b][/b] for bold text
Email:
+ Ask a question
If you have a technical question related to programming and computers, ask it here. Other users will help you solve it!
Unanswered questions
Share your knowledge by helping others solve their problems