核心思想
无论使用哪种技术,Java Web 导出 Excel 的核心流程都是一样的:

- 后端准备数据:在 Java 后端,你需要将需要导出的数据查询出来,并组织成一个结构化的形式(一个 List
主流技术方案对比
| 特性 | Apache POI | EasyExcel (阿里巴巴) |
|---|---|---|
| 简介 | Java 操作 Office 格式文件最经典、最强大的库,功能全面。 | 阿里巴巴开源,基于 POI 优化,解决了 POI 内存消耗大的问题,主打低内存。 |
| 优点 | 功能强大,支持所有 Excel 版本和复杂格式(如公式、图表、宏)。 社区成熟,资料丰富。 |
内存占用极低,解决了 POI 的 OOM 问题,适合大数据量导出。 API 设计更简洁,使用方便。 读写性能优秀。 |
| 缺点 | 对于大数据量(如 10 万行以上),使用 SXSSFWorkbook 模式依然复杂,且容易 OOM。API 相对繁琐。 |
功能相对 POI 较弱,不支持非常复杂的 Excel 特性(如 VBA 宏)。 社区规模小于 POI。 |
| 适用场景 | - 需要生成复杂格式、模板的 Excel。 - 数据量不大或可控的场景。 |
- 大数据量导出(推荐首选)。 - 对内存敏感的场景。 - 普通的数据导出需求。 |
| 依赖 | poi, poi-ooxml |
easyexcel |
- 新项目、数据量大:强烈推荐使用 EasyExcel,它更简单、更高效。
- 需要处理复杂模板、格式:可以考虑 Apache POI。
- 老项目,已经使用 POI 且无性能问题:可以继续使用 POI。
实战演练
下面我们分别用 EasyExcel 和 Apache POI 来实现一个简单的数据导出功能。
场景:导出一个用户列表
数据模型:
// User.java
public class User {
private String name;
private Integer age;
private String email;
// 构造方法、Getter 和 Setter
public User(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
使用 EasyExcel (推荐)
EasyExcel 的核心优势在于其注解驱动和低内存模型。
添加 Maven 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version> <!-- 建议使用最新版本 -->
</dependency>
创建实体类并添加注解
为了方便 Excel 的列名映射,我们在 User 类上添加 EasyExcel 的注解。

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
// @ColumnWidth 用于设置列宽,可选
@ColumnWidth(20)
public class User {
// value = "姓名" 表示 Excel 表头
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private Integer age;
@ExcelProperty("邮箱")
private String email;
// 构造方法、Getter 和 Setter...
// 省略...
}
编写 Controller 或 Service 层代码
这是导出的核心逻辑。
import com.alibaba.excel.EasyExcel;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/excel")
public class ExcelExportController {
@GetMapping("/export/easyexcel")
public void exportEasyExcel(HttpServletResponse response) throws IOException {
// 1. 准备数据
List<User> userList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
userList.add(new User("用户" + i, 20 + i, "user" + i + "@example.com"));
}
// 2. 设置响应头
// 设置内容类型为 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// 设置编码,防止中文乱码
String fileName = URLEncoder.encode("用户列表_EasyExcel", "UTF-8");
// 设置响应头,告诉浏览器这是一个附件,需要下载
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 3. 写入 Excel
// EasyExcel.write(response.getOutputStream(), User.class).sheet("用户数据").doWrite(userList);
try {
EasyExcel.write(response.getOutputStream(), User.class)
.sheet("用户列表") // sheet 名称
.doWrite(userList); // 写入数据
} catch (Exception e) {
// 处理异常,比如流关闭等
e.printStackTrace();
}
}
}
代码解释:
response.setContentType: 告诉浏览器文件类型是.xlsx。URLEncoder.encode: 非常重要! 用于处理中文文件名,防止乱码。response.setHeader("Content-disposition", "attachment;filename=..."): 这是触发下载的关键。EasyExcel.write(...): 核心API,指定输出流、数据模型类。.sheet("..."): 指定工作表的名称。.doWrite(userList): 执行写入操作。
使用 Apache POI
POI 是传统方案,API 更底层,但功能更强大。
添加 Maven 依赖
<!-- POI 核心库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version> <!-- 建议使用最新版本 -->
</dependency>
<!-- POI 对应 Office 2007+ 格式的支持 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
编写 Controller 或 Service 层代码
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/excel")
public class ExcelExportController {
@GetMapping("/export/poi")
public void exportPoi(HttpServletResponse response) throws IOException {
// 1. 准备数据
List<User> userList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
userList.add(new User("用户" + i, 20 + i, "user" + i + "@example.com"));
}
// 2. 创建 Excel 工作簿
Workbook workbook = new XSSFWorkbook(); // 使用 XSSFWorkbook 生成 .xlsx 格式
Sheet sheet = workbook.createSheet("用户列表_POI");
// 3. 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("姓名");
headerRow.createCell(1).setCellValue("年龄");
headerRow.createCell(2).setCellValue("邮箱");
// 4. 填充数据
int rowNum = 1;
for (User user : userList) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(user.getName());
row.createCell(1).setCellValue(user.getAge());
row.createCell(2).setCellValue(user.getEmail());
}
// 5. 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
String fileName = URLEncoder.encode("用户列表_POI", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 6. 输出到响应流
try {
workbook.write(response.getOutputStream());
workbook.close(); // 关闭工作簿,释放资源
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码解释:

XSSFWorkbook: 用于创建.xlsx格式的工作簿,如果是.xls,则使用HSSFWorkbook。workbook.createSheet(): 创建工作表。sheet.createRow(): 创建行。row.createCell(): 创建单元格并赋值。workbook.write(): 将工作簿内容写入输出流。
高级技巧与常见问题
大数据量导出 (EasyExcel)
EasyExcel 的核心优势就在于此,它采用磁盘缓存的方式,避免将所有数据加载到内存中。
// 假设你有一个从数据库分页查询数据的方法
// public List<User> queryUsersByPage(int pageNum, int pageSize);
@GetMapping("/export/big-data")
public void exportBigData(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
String fileName = URLEncoder.encode("大数据量列表", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// EasyExcel.write 会自动处理分页和流式写入
EasyExcel.write(response.getOutputStream(), User.class)
.sheet("大数据")
.doWrite(() -> {
// 这里返回一个迭代器或 Lambda,EasyExcel 会循环调用它获取数据
// 模拟一个大数据源
List<User> allData = new ArrayList<>();
for (int i = 0; i < 100000; i++) { // 模拟10万条数据
allData.add(new User("BigUser" + i, 30, "big" + i + "@test.com"));
}
return allData; // 实际项目中,这里应该是分页查询数据库的逻辑
});
}
最佳实践:不要一次性查出所有数据(SELECT * FROM users),而是在 Lambda 中实现分页查询逻辑,每次只查一页数据,返回给 EasyExcel,这样可以极大地降低内存峰值。
使用模板导出
有时,Excel 的格式(如合并单元格、固定标题、Logo、样式)是固定的,这时就需要使用模板。
EasyExcel 模板导出:
- 准备一个 Excel 模板文件(
template.xlsx),用{{xxx}}作为占位符。 - 将模板文件放在
resources目录下。 - 在代码中读取模板并填充数据。
// 假设模板文件 template.xlsx 中有三个列,分别用 {{name}}, {{age}}, {{email}} 占位
@GetMapping("/export/template")
public void exportTemplate(HttpServletResponse response) throws IOException {
// 准备数据
List<User> userList = new ArrayList<>();
// ... 省略数据填充
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
String fileName = URLEncoder.encode("模板导出", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 从 classpath 加载模板
ClassPathResource resource = new ClassPathResource("template.xlsx");
// 使用模板进行写入
EasyExcel.write(response.getOutputStream(), User.class)
.withTemplate(resource.getInputStream())
.sheet()
.doWrite(userList);
}
常见问题:中文乱码
- 文件名乱码:使用
URLEncoder.encode(fileName, "UTF-8")进行编码。 - Excel 内容乱码:
- 确保你的项目源文件编码是
UTF-8。 - 确保数据库中的数据编码是正确的。
- 使用 EasyExcel 时,确保实体类的字段是
String类型,如果数据库返回的是Blob或其他类型,需要先正确转换为String。
- 确保你的项目源文件编码是
文件名含空格或特殊字符
URLEncoder.encode 会将空格编码为 ,这在某些浏览器上可能无法正确识别,可以手动替换掉 。
String fileName = URLEncoder.encode("用户 列表", "UTF-8").replaceAll("\\+", "%20");
| 特性 | EasyExcel (推荐) | Apache POI |
|---|---|---|
| 上手难度 | 简单 | 中等 |
| 大数据量 | 优秀,内存占用低 | 差,容易 OOM |
| 功能丰富度 | 良好,满足 90% 需求 | 卓越,支持所有复杂功能 |
| API 风格 | 现代化,链式调用 | 传统,面向对象 |
对于绝大多数 Java Web 开发场景,EasyExcel 是目前更优的选择,它简单、高效,能解决大部分导出需求,尤其是在处理大数据量时优势明显,只有在遇到非常特殊的 Excel 格式要求时,才需要考虑使用功能更全面的 Apache POI。
