Update: if you're using Servlet 3.0 or newer, then there are built-in ways to process file uploads. You may find it more useful: Uploading files in Servlet 3.0.
Downloading files is made relatively easy using a FileServlet, but uploading files is a bit harder. Entering/selecting the raw absolute file path in input type="text" and sending it to the server so that it can be used in a File object isn't going to work, as the server doesn't have access to the client's file system. That will work only if the server as well as the client runs on the same machine and that wouldn't occur in real life.
To browse and select a file for upload you need a input type="file" field in the form. As stated in the HTML specification you have to use the POST method and the enctype attribute of the form have to be set to "multipart/form-data".
<form action="myServlet" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
After submitting such a form the binary multipart form data is available in the HttpServletRequest#getInputStream(). For testing purposes you can read the stream using the following snippet:
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
Parsing such a stream requires precise background knowledge of how multipart form data requests are structured. The standard Servlet API namely doesn't provide builtin facilities to parse them. The form fields aren't available as parameter of the request (e.g. request.getParameter("name") will always return null), they are included in the binary stream. The uploaded files are also included in the binary stream. To create a perfect multipart parser you'll have to write a lot of code. But don't feel disappointed, there are lot of 3rd party multipart parsers available. A commonly used one is the Apache Commons FileUpload. It can parse the multipart form data into several FileItem objects (misleading class name by the way, I'd rather call it MultipartItem). You'll have to filter the parameters and files out yourself.
To save you the effort, I've played around with it a while and wrote a MultipartFilter which automatically detects if the request is a multipart form data request, parses the request and store the parameters back in the parameter map of HttpServletRequest and stores the uploaded files as attributes of the HttpServletRequest. This way you can just continue writing the Servlet logic as usual.
It makes use of the Apache Commons FileUpload API 1.2. So you need at least the following JAR's (newer versions are allowed) in the classpath, e.g. in /WEB-INF/lib:
Here is the code. The stuff is tested in a Java EE 5.0 environment with Tomcat 6.0 with Servlet 2.5, JSP 2.1 and JSTL 1.2.
package net.balusc.webapp;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class MultipartFilter implements Filter {
private long maxFileSize;
public void init(FilterConfig filterConfig) throws ServletException {
String maxFileSize = filterConfig.getInitParameter("maxFileSize");
if (maxFileSize != null) {
if (!maxFileSize.matches("^\\d+$")) {
throw new ServletException("MultipartFilter 'maxFileSize' is not numeric.");
}
this.maxFileSize = Long.parseLong(maxFileSize);
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException
{
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletRequest parsedRequest = parseRequest(httpRequest);
chain.doFilter(parsedRequest, response);
} else {
chain.doFilter(request, response);
}
}
public void destroy() {
}
@SuppressWarnings("unchecked")
private HttpServletRequest parseRequest(HttpServletRequest request) throws ServletException {
if (!ServletFileUpload.isMultipartContent(request)) {
return request;
}
List<FileItem> multipartItems = null;
try {
multipartItems = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
} catch (FileUploadException e) {
throw new ServletException("Cannot parse multipart request: " + e.getMessage());
}
Map<String, String[]> parameterMap = new HashMap<String, String[]>();
for (FileItem multipartItem : multipartItems) {
if (multipartItem.isFormField()) {
processFormField(multipartItem, parameterMap);
} else {
processFileField(multipartItem, request);
}
}
return wrapRequest(request, parameterMap);
}
private void processFormField(FileItem formField, Map<String, String[]> parameterMap) {
String name = formField.getFieldName();
String value = formField.getString();
String[] values = parameterMap.get(name);
if (values == null) {
parameterMap.put(name, new String[] { value });
} else {
int length = values.length;
String[] newValues = new String[length + 1];
System.arraycopy(values, 0, newValues, 0, length);
newValues[length] = value;
parameterMap.put(name, newValues);
}
}
private void processFileField(FileItem fileField, HttpServletRequest request) {
if (fileField.getName().length() <= 0) {
request.setAttribute(fileField.getFieldName(), null);
} else if (maxFileSize > 0 && fileField.getSize() > maxFileSize) {
request.setAttribute(fileField.getFieldName(), new FileUploadException(
"File size exceeds maximum file size of " + maxFileSize + " bytes."));
fileField.delete();
} else {
request.setAttribute(fileField.getFieldName(), fileField);
}
}
private static HttpServletRequest wrapRequest(
HttpServletRequest request, final Map<String, String[]> parameterMap)
{
return new HttpServletRequestWrapper(request) {
public Map<String, String[]> getParameterMap() {
return parameterMap;
}
public String[] getParameterValues(String name) {
return parameterMap.get(name);
}
public String getParameter(String name) {
String[] params = getParameterValues(name);
return params != null && params.length > 0 ? params[0] : null;
}
public Enumeration<String> getParameterNames() {
return Collections.enumeration(parameterMap.keySet());
}
};
}
}
Add and configure the filter as follows in the web.xml:
<filter>
<description>
Check for multipart HttpServletRequests and parse the multipart form data so that all
regular form fields are available in the parameterMap of the HttpServletRequest and that
all form file fields are available as attribute of the HttpServletRequest. The attribute
value of a form file field can be an instance of FileItem or FileUploadException.
</description>
<filter-name>multipartFilter</filter-name>
<filter-class>net.balusc.webapp.MultipartFilter</filter-class>
<init-param>
<description>
Sets the maximum file size of the uploaded file in bytes. Set to 0 to indicate an
unlimited file size. The example value of 1048576 indicates a maximum file size of
1MB. This parameter is not required and can be removed safely.
</description>
<param-name>maxFileSize</param-name>
<param-value>1048576</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
That's all, folks!
Here is a basic use example of a servlet, a form and JSP file which demonstrates the working of the MultipartFilter. Thanks to the MultipartFilter you can just use HttpServletRequest#getParameter() and #getParameterValues() for regular form fields. The uploaded file is available by HttpServletRequest#getAttribute(). If it is an instance of FileItem, then the upload was succesful, else if it is an instance of FileUploadException, then the upload was failed. The only cause can be that the file size exceeded the configured maximum file size.
package mypackage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.io.FilenameUtils;
public class MyServlet extends HttpServlet {
private File uploadFilePath;
public void init() throws ServletException {
String uploadFilePathParam = getServletConfig().getInitParameter("uploadFilePath");
if (uploadFilePathParam == null) {
throw new ServletException("MyServlet 'uploadFilePath' is not configured.");
}
uploadFilePath = new File(uploadFilePathParam);
if (!uploadFilePath.exists()) {
throw new ServletException("MyServlet 'uploadFilePath' does not exist.");
}
if (!uploadFilePath.isDirectory()) {
throw new ServletException("MyServlet 'uploadFilePath' is not a directory.");
}
if (!uploadFilePath.canWrite()) {
throw new ServletException("MyServlet 'uploadFilePath' is not writeable.");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
MyForm myForm = new MyForm();
process(request, myForm);
request.setAttribute("myForm", myForm);
forward(request, response);
}
private void process(HttpServletRequest request, MyForm myForm) {
String text = request.getParameter("text");
if (isEmpty(text)) {
myForm.setError("text", "Please enter some text.");
}
Object fileObject = request.getAttribute("file");
if (fileObject == null) {
myForm.setError("file", "Please select file to upload.");
} else if (fileObject instanceof FileUploadException) {
FileUploadException fileUploadException = (FileUploadException) fileObject;
myForm.setError("file", fileUploadException.getMessage());
}
String[] check = request.getParameterValues("check");
if (isEmpty(check)) {
myForm.setError("check", "Please check one or more checkboxes.");
}
if (!myForm.hasErrors()) {
FileItem fileItem = (FileItem) fileObject;
String fileName = FilenameUtils.getName(fileItem.getName());
String prefix = FilenameUtils.getBaseName(fileName) + "_";
String suffix = "." + FilenameUtils.getExtension(fileName);
try {
File file = File.createTempFile(prefix, suffix, uploadFilePath);
fileItem.write(file);
myForm.setFile(file);
} catch (Exception e) {
myForm.setError("file", e.getMessage());
e.printStackTrace();
}
}
if (!myForm.hasErrors()) {
myForm.setMessage("text", "You have entered: " + text + ".");
myForm.setMessage("file", "File succesfully uploaded.");
myForm.setMessage("check", "You have checked: " + Arrays.toString(check) + ".");
}
}
private void forward(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
request.getRequestDispatcher("myForm.jsp").forward(request, response);
}
public static boolean isEmpty(Object value) {
if (value == null) {
return true;
} else if (value instanceof String) {
return ((String) value).trim().length() == 0;
} else if (value instanceof Object[]) {
return ((Object[]) value).length == 0;
} else if (value instanceof Collection<?>) {
return ((Collection<?>) value).size() == 0;
} else if (value instanceof Map<?, ?>) {
return ((Map<?, ?>) value).size() == 0;
} else {
return value.toString() == null || value.toString().trim().length() == 0;
}
}
}
Add and configure the servlet as follows in the web.xml:
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>mypackage.MyServlet</servlet-class>
<init-param>
<description>
Set the file path where uploaded files should be stored in. This parameter is
required.
</description>
<param-name>uploadFilePath</param-name>
<param-value>c:/upload</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/myServlet</url-pattern>
</servlet-mapping>
The form bean:
package mypackage;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class MyForm {
private String text;
private File file;
private String[] check;
private Map<String, Boolean> checked = new HashMap<String, Boolean>();
private Map<String, String> errors = new HashMap<String, String>();
private Map<String, String> messages = new HashMap<String, String>();
public String getText() {
return text;
}
public File getFile() {
return file;
}
public String[] getCheck() {
return check;
}
public void setText(String text) {
this.text = text;
}
public void setFile(File file) {
this.file = file;
}
public void setCheck(String[] check) {
checked = new HashMap<String, Boolean>();
for (String value : check) {
checked.put(value, Boolean.TRUE);
}
this.check = check;
}
public Map<String, Boolean> getChecked() {
return checked;
}
public Map<String, String> getErrors() {
return errors;
}
public Map<String, String> getMessages() {
return messages;
}
public void setError(String fieldName, String message) {
errors.put(fieldName, message);
}
public void setMessage(String fieldName, String message) {
messages.put(fieldName, message);
}
public boolean hasErrors() {
return errors.size() > 0;
}
}
Finally the JSP file, save it as myForm.jsp in the root of the WebContent:
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!doctype html>
<html lang="en">
<head>
<title>Test</title>
</head>
<body>
<jsp:useBean id="myForm" class="mypackage.MyForm" scope="request" />
<jsp:setProperty name="myForm" property="*" />
<form action="myServlet" method="post" enctype="multipart/form-data">
<label for="text" >Text:</label>
<input type="text" id="text" name="text" value="${myForm.text}">
<c:if test="${myForm.errors.text != null}">
<span style="color: red;">${myForm.errors.text}</span>
</c:if>
<c:if test="${myForm.messages.text != null}">
<span style="color: green;">${myForm.messages.text}</span>
</c:if>
<br>
<label for="file" >File:</label>
<input type="file" id="file" name="file">
<c:if test="${myForm.errors.file != null}">
<span style="color: red;">${myForm.errors.file}</span>
</c:if>
<c:if test="${myForm.messages.file != null}">
<span style="color: green;">${myForm.messages.file}
<c:if test="${myForm.file != null}">
<a href="file/${myForm.file.name}">Download back</a>.
</c:if>
</span>
</c:if>
<br>
<label for="check1" >Check 1:</label>
<input type="checkbox" id="check1" name="check" value="check1"
${myForm.checked.check1 ? 'checked' : ''}>
<c:if test="${myForm.errors.check != null}">
<span style="color: red;">${myForm.errors.check}</span>
</c:if>
<c:if test="${myForm.messages.check != null}">
<span style="color: green;">${myForm.messages.check}</span>
</c:if>
<br>
<label for="check2" >Check 2:</label>
<input type="checkbox" id="check2" name="check" value="check2"
${myForm.checked.check2 ? 'checked' : ''} /></td>
<br>
<input type="submit">
</form>
</body>
</html>
Note: the download link makes use of the FileServlet. Make sure that it points to the same directory as where the file is uploaded. You can configure it as an init-param.
Copy'n'paste the stuff, run it on http://localhost:8080/playground/myServlet (assuming that your local development server runs at port 8080 and that the context root of your playground web application project is called 'playground') and see it wonderfully working!
Copyright - GNU Lesser General Public License
(C) November 2007, BalusC