Java Servlet File Upload & Download: Solving Chinese Filename Encoding Issues

This tutorial demonstrates implementing file upload and download using Java Servlets with Apache Commons FileUpload, including a CharacterEncodingFilter to handle UTF-8 encoding and resolve Chinese filename corruption during both upload and download operations.

Java Captain
Java Captain
Java Captain
Java Servlet File Upload & Download: Solving Chinese Filename Encoding Issues

Project Setup

A Maven project is created with the following dependencies:

javax.servlet-api 3.1.0 (provided scope)

commons-fileupload 1.3.1 for multipart parsing

commons-lang3 3.12.0 for string utilities

The tomcat7-maven-plugin 2.2 is configured to run on port 80 with context path '/'.

<dependencies>
  <!--servlet支持-->
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
  </dependency>
  <!--文件上传组件支持-->
  <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
  </dependency>
  <!--Apache提供的一些实用的工具类支持-->
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
  </dependency>
</dependencies>

<build>
  <finalName>fileUpDownTest</finalName>
  <plugins>
    <!--Tomcat官网最新版本的tomcat插件-->
    <plugin>
      <groupId>org.apache.tomcat.maven</groupId>
      <artifactId>tomcat7-maven-plugin</artifactId>
      <version>2.2</version>
      <configuration>
        <!--tomcat启动的端口号-->
        <port>80</port>
        <!--网站启动后访问的虚拟地址-->
        <path>/</path>
      </configuration>
    </plugin>
  </plugins>
</build>

Project Structure

The project layout:

com.jobs.controller – contains upLoadServlet and downLoadServlet

com.jobs.filter – contains CharacterEncodingFilter

webapp/upload – stores uploaded files; pre‑seeded with a Chinese‑named txt file for download testing

webapp/index.html – entry page with upload and download forms

project structure
project structure

CharacterEncodingFilter

A filter mapped to /api/* sets request and response encoding to UTF‑8 via init parameters, preventing garbled characters for servlet requests while leaving static resources (CSS, JS) untouched.

package com.jobs.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//这里只监听 /api/* 的请求,如果你想监听所有请求,可以使用 /* 配置
//另外配置了过滤器的请求和响应的初始化参数,在下面的处理方法中可以获取
@WebFilter(value = "/api/*",
    initParams = {
        @WebInitParam(name = "requestEncoding", value = "UTF-8"),
        @WebInitParam(name = "responseContentType", value = "text/html;charset=UTF-8")})
public class CharacterEncodingFilter implements Filter {

    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) resp;

            //获取初始化参数
            String requestEncoding = filterConfig.getInitParameter("requestEncoding");
            String responseContentType = filterConfig.getInitParameter("responseContentType");

            //使用初始化参数,设置请求和响应的编码
            request.setCharacterEncoding(requestEncoding);
            response.setContentType(responseContentType);

            chain.doFilter(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void destroy() {
    }
}

upLoadServlet – File Upload

Mapped to /api/upload, handles POST with multipart/form-data. Uses DiskFileItemFactory and ServletFileUpload to parse the request. Form fields are read with UTF‑8; files are saved under webapp/upload using their original names (Chinese names preserved thanks to the filter).

package com.jobs.controller;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;

//配置访问地址
@WebServlet("/api/upload")
public class upLoadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse resp) throws IOException {
        //判断该操作是否支持文件上传操作
        //要提交的form表单上必须要有属性 enctype="multipart/form-data"
        if (ServletFileUpload.isMultipartContent(request)) {
            try {
                //将文件保存到 webapp 下的 upload 文件夹中
                String dir = this.getServletContext().getRealPath("upload");
                File f = new File(dir);
                if (!f.exists()) {
                    f.mkdir();
                }

                //采用 apache 的上传组件,只需要编写很少的代码,即可完成文件上传功能
                DiskFileItemFactory factory = new DiskFileItemFactory();
                ServletFileUpload fileUpload = new ServletFileUpload(factory);
                List<FileItem> fileItems = fileUpload.parseRequest(request);

                for (FileItem item : fileItems) {
                    //当前表单是否是文件表单
                    if (item.isFormField()) {
                        //获取表单字段
                        String fieldName = item.getFieldName();
                        //必须使用 utf-8 编码获取表单字段的值,否则就是乱码
                        String fieldValue = item.getString("utf-8");
                        System.out.println("表单字段:" + fieldName + "," + fieldValue);
                    } else {
                        //此 item 表示文件
                        //获取文件的表单字段名称
                        String fieldName = item.getFieldName();
                        //获取文件的原始文件名,不包含路径
                        String fileName = item.getName();
                        System.out.println("上传文件:" + fieldName + "," + fileName);
                        //这里的 StringUtils 是 apache 的 commons-lang3 组件提供的实用工具类
                        if (StringUtils.isNotEmpty(fileName)) {
                            //将上传的文件,以原始名称保存到具体的目录下,
                            //当然你可以生成随机 uuid 给文件命名
                            //这里使用原始文件名,主要演示由于设置了过滤器统一处理字符编码,
                            //所以保存中文文件名,不出现乱码的问题
                            item.write(new File(dir, fileName));
                        }
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        //由于过滤器统一设置了响应编码,
        //所以响应页面中展示的中文,不会出现乱码
        resp.getWriter().write("恭喜你,成功了");
    }
}

Note: When using the Tomcat Maven plugin, uploaded files appear directly in the source webapp/upload folder. With a standalone Tomcat deployment, check the deployed webapp's upload directory.

downLoadServlet – File Download

Mapped to /api/download, handles POST. Accepts a downLoadName parameter (optional) to customize the downloaded filename; defaults to "测试文件.txt". Two download approaches are shown (the second is commented out).

Approach 1 – Direct file stream: Sets response content type to application/octet-stream;charset=utf-8. The Chinese filename is encoded to ISO‑8859‑1 for the Content-Disposition header to avoid corruption. Reads the file from webapp/upload/测试文件.txt via BufferedInputStream and writes to ServletOutputStream in 1024‑byte chunks.

Approach 2 – In‑memory stream (commented): Reads the same file into a ByteArrayOutputStream then writes to the response. This pattern is useful when generating content on‑the‑fly (e.g., Excel from database) without creating a temporary disk file.

package com.jobs.controller;

import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

//配置访问地址
@WebServlet("/api/download")
public class downLoadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req,
                          HttpServletResponse response) throws ServletException, IOException {
        String parm = req.getParameter("downLoadName");
        String downLoadName;
        if (StringUtils.isNotBlank(parm)) {
            downLoadName = parm + ".txt";
        } else {
            downLoadName = "测试文件.txt";
        }

        //获取所要下载的文件,这里只是 demo 演示,因此对下载的目标文件进行了硬编码
        String dir = this.getServletContext().getRealPath("upload");
        File f = new File(dir, "测试文件.txt");

        //下载文件的响应类型,这里统一设置成了文件流
        //你可以根据自己所提供下载的文件类型,使用不同的响应 mime 类型
        response.setContentType("application/octet-stream;charset=utf-8");
        //设置下载弹出框中默认显示的文件名称,如果指定中文名称的话,需要转成 iso8859-1 编码,解决乱码问题
        String fileName = new String(downLoadName.getBytes(), "iso8859-1");
        response.addHeader("Content-Disposition", "attachment;filename=" + fileName);

        //第一种下载方式,适合文件已经在硬盘上,此时读取文件字节流,直接下载
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f))) {
            ServletOutputStream outputStream = response.getOutputStream();
            byte[] bArr = new byte[1024];
            int len;
            while ((len = bis.read(bArr)) != -1) {
                outputStream.write(bArr, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        /*
        //第二种下载方式,适合不在硬盘上生成文件,而是在内存中生成文件字节流,然后下载
        //当然这里的例子,还是从硬盘上读取了文件,但是将字节流存入 ByteArrayOutputStream 内存字节流
        //模拟使用 ByteArrayOutputStream 生成文件流,然后进行下载
        ServletOutputStream outputStream = response.getOutputStream();
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));
             ByteArrayOutputStream baos=new ByteArrayOutputStream()){
            byte[] bArr = new byte[1024];
            int len;
            while ((len = bis.read(bArr)) != -1) {
                baos.write(bArr, 0, len);
            }
            baos.writeTo(outputStream);
        } catch(IOException e){
            e.printStackTrace();
        }
        outputStream.flush();
        */
    }
}

Front‑End Page (index.html)

Contains two forms:

Upload form ( action="/api/upload", enctype="multipart/form-data"): text input submitName, two file inputs upFile1 and upFile2, submit/reset buttons.

Download form ( action="/api/download"): text input downLoadName (no extension), submit button "下载测试文件".

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>上传文件测试</title>
</head>
<body>
<form id="Form1" action="/api/upload" method="post" enctype="multipart/form-data">
  <table>
    <caption>表单提交,文件上传,支持中文文件名称</caption>
    <tr>
      <td>请输入内容:</td>
      <td><input type="text" name="submitName"></td>
    </tr>
    <tr>
      <td>请选择文件01:</td>
      <td><input type="file" name="upFile1"></td>
    </tr>
    <tr>
      <td>请选择文件02:</td>
      <td><input type="file" name="upFile2"></td>
    </tr>
    <tr>
      <td></td>
      <td>
        <button type="submit">提交</button>
        <button type="reset">重置</button>
      </td>
    </tr>
  </table>
</form>
<br/>
<form id="Form2" action="/api/download" method="post">
  <table>
    <caption>文件下载,支持中文文件名称</caption>
    <tr>
      <td>请输入下载后的文件命名(不需要输入后缀名):</td>
      <td><input type="text" name="downLoadName"></td>
    </tr>
    <tr>
      <td>点击按钮下载中文名称的文件:</td>
      <td>
        <button type="submit">下载测试文件</button>
      </td>
    </tr>
  </table>
</form>
</body>
</html>
UI screenshot
UI screenshot

The UI demonstrates that Chinese characters in form fields and filenames are correctly handled during both upload and download, with no garbled output.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

MavenFile UploadFile DownloadTomcatCharacter EncodingApache Commons FileUploadJava ServletChinese Filename
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.