| name | easyexcel-write |
| description | 基于 Alibaba EasyExcel(com.alibaba:easyexcel:4.0.3)的 Excel 数据写入技能。覆盖最简写入(3 种写法)、复杂表头(多级)、@ExcelProperty/@ExcelIgnore/@DateTimeFormat/@NumberFormat 注解、includeColumn/excludeColumn 选择列、@ColumnWidth/@ContentRowHeight 样式注解、@HeadStyle/@ContentStyle 自定义样式、合并单元格(@ContentLoopMerge/LoopMergeStrategy)、图片导出(File/InputStream/URL/byte[])、超链接/批注/公式/富文本(WriteCellData)、动态表头、自动列宽、自定义拦截器(CellWriteHandler/SheetWriteHandler/RowWriteHandler)、分页分批写入、Web 下载直接写 OutputStream、03/07 版兼容。当用户需要程序化生成 Excel(无预制模板)导出订单/用户/财务/库存/报表/对账单/审批单等结构化数据时使用此技能,不适用于按预制模板填充(改用 easyexcel-fill)、不适用于读取 Excel(改用 easyexcel-read)。 |
| license | Apache-2.0 |
EasyExcel 数据写入(Write)
官方文档:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write
官方示例:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java
当前版本:com.alibaba:easyexcel:4.0.3(Apache-2.0,仓库已归档,仅做 Bug 修复)
本技能专注于 "程序化生成 Excel" 场景。当用户没有预制模板、要求用 Java 对象/Map 直接生成结构化 Excel(订单、用户、财务、库存、报表、对账单、审批单)时,应当使用本技能。
Capability Boundaries
✅ 强项
- 最简写入(JDK8+ 写法、传统写法、Builder 写法)
- 复杂表头(多级表头
@ExcelProperty({"主标题", "副标题"}))
- 选择性导出列(
includeColumnFiledNames / excludeColumnFiledNames)
- 注解定义格式(
@ColumnWidth @ContentRowHeight @DateTimeFormat @NumberFormat)
- 自定义样式(注解方式 / 拦截器方式 /
HorizontalCellStyleStrategy)
- 合并单元格(
@ContentLoopMerge / LoopMergeStrategy)
- 图片导出(File/InputStream/URL/byte[] 多种来源)
- 超链接/批注/公式/富文本(
WriteCellData 体系)
- 动态表头(运行时决定表头)
- 多次写入/多 Sheet/多 Table
- Web 直接输出到
HttpServletResponse
⚠️ 限制
- 单 Sheet 建议 < 5000 行一次性写入(数据量大用分批)
- 样式对象最多创建 6W 个,否则 Excel 打不开
- 图片会驻内存,超大图建议上传到 OSS
- 批注需要
inMemory(true)
- 03 版(
.xls)兼容性需用 excelType(ExcelTypeEnum.XLS)
❌ Out of Scope(不该用本技能的场景,请改用其它技能)
- 按预制模板填充 → 不适用本技能,使用
easyexcel-fill
- 读取 Excel 数据 → 不适用本技能,使用
easyexcel-read
- CSV 文件处理 → 不适用本技能,自行使用 commons-csv / OpenCSV
- Word/PPT → 不适用本技能,使用 Apache POI / Apache POI-XML
Data Privacy
本技能不收集、存储或传输任何用户数据。所有代码示例仅用于本地开发参考。
When to use this skill
- 用户说"导出 Excel"、"生成 Excel"、"下载 Excel"、"导出订单/用户/财务/库存"
- 没有预制模板,需要程序化生成结构化 Excel
- 业务上有"分 Sheet(按月份/状态/类型)"、"分 Table(多个表块)"等需求
- 需要导出图片、超链接、公式、富文本、合并单元格
Quick Start
典型调用方式(3 种写法):
EasyExcel.write(fileName, DemoData.class)
.sheet("模板")
.doWrite(() -> data());
List<DemoData> data = data();
EasyExcel.write(fileName, DemoData.class)
.sheet("模板")
.doWrite(data);
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
}
官方原文:"数据量建议 5000 以内"(单 sheet 一次性写入推荐上限)。
Workflow
Step 1. 定义数据模型 — 编写 DTO/VO,标注 @ExcelProperty 等注解
Step 2. 选择写入方式 — 单次 doWrite() / 多次 excelWriter.write() / 多次 doWrite() 循环
Step 3. 配置 Builder — 包含列选择、表头、样式、注册拦截器等
Step 4. 构造数据 — 准备 List<DTO> / List<List<String>>
Step 5. 执行写入 — try-with-resources 关闭 ExcelWriter
Step 6. 验证输出 — 打开生成的 .xlsx 检查表头/样式/格式/合并
Critical: 注解速查
| 注解 | 用途 | 关键属性 |
|---|
@ExcelProperty | 指定列名/索引/转换器 | value, index, order, converter |
@ExcelIgnore | 忽略该字段 | —— |
@ExcelIgnoreUnannotated | 类级:未标注字段不参与读写 | —— |
@DateTimeFormat | 日期格式 | value, use1904windowing |
@NumberFormat | 数字格式 | value, roundingMode |
@ColumnWidth | 列宽 | value |
@HeadRowHeight | 表头行高 | value |
@ContentRowHeight | 内容行高 | value |
@HeadStyle | 表头样式(POI 风格) | fillPatternType, fillForegroundColor, font 等 |
@HeadFontStyle | 表头字体 | fontHeightInPoints, color, bold 等 |
@ContentStyle | 内容样式 | 同 HeadStyle |
@ContentFontStyle | 内容字体 | 同 HeadFontStyle |
@ContentLoopMerge | 循环合并 | eachRow |
@OnceAbsoluteMerge | 一次性绝对合并 | firstRowIndex lastRowIndex 等 |
官方原文:order 默认 Integer.MAX_VALUE;优先级:index > order > value。
官方原文:"不建议 index 和 name 同时用,要么只用index,要么只用name匹配"
Critical: 3 种基础写入模式
模式 1:最简写入(推荐)
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
@ExcelIgnore
private String ignore;
}
EasyExcel.write(fileName, DemoData.class)
.sheet("模板")
.doWrite(data());
模式 2:选择性导出列
Set<String> exclude = new HashSet<>();
exclude.add("date");
EasyExcel.write(fileName, DemoData.class)
.excludeColumnFiledNames(exclude)
.sheet("模板").doWrite(data());
Set<String> include = new HashSet<>();
include.add("date");
EasyExcel.write(fileName, DemoData.class)
.includeColumnFiledNames(include)
.sheet("模板").doWrite(data());
模式 3:多次写入(同一 Sheet / 不同 Sheet / 不同对象)
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
for (int i = 0; i < 5; i++) {
excelWriter.write(data(), writeSheet);
}
for (int i = 0; i < 5; i++) {
WriteSheet sheet = EasyExcel.writerSheet(i, "模板" + i).build();
excelWriter.write(data(), sheet);
}
}
Critical: 复杂表头与格式
多级表头
@Getter
@Setter
@EqualsAndHashCode
public class ComplexHeadData {
@ExcelProperty({"主标题", "字符串标题"})
private String string;
@ExcelProperty({"主标题", "日期标题"})
private Date date;
@ExcelProperty({"主标题", "数字标题"})
private Double doubleData;
}
数字/日期格式化
@Getter
@Setter
@EqualsAndHashCode
public class ConverterData {
@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
private String string;
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@ExcelProperty("日期标题")
private Date date;
@NumberFormat("#.##%")
@ExcelProperty("数字标题")
private Double doubleData;
}
列宽与行高
@Getter
@Setter
@EqualsAndHashCode
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
@ExcelProperty("字符串标题")
private String string;
@ColumnWidth(50)
@ExcelProperty("数字标题")
private Double doubleData;
}
Critical: 自定义样式
注解方式(since 2.2.0-beta1)
@Data
@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 10)
@HeadFontStyle(fontHeightInPoints = 20)
@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 17)
@ContentFontStyle(fontHeightInPoints = 20)
public class DemoStyleData {
@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 14)
@HeadFontStyle(fontHeightInPoints = 30)
@ExcelProperty("字符串标题")
private String string;
}
拦截器方式(最灵活)
WriteCellStyle headStyle = new WriteCellStyle();
headStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
WriteFont headFont = new WriteFont();
headFont.setFontHeightInPoints((short) 20);
headStyle.setWriteFont(headFont);
WriteCellStyle contentStyle = new WriteCellStyle();
contentStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
contentStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
HorizontalCellStyleStrategy strategy =
new HorizontalCellStyleStrategy(headStyle, contentStyle);
EasyExcel.write(fileName, DemoData.class)
.registerWriteHandler(strategy)
.sheet("模板").doWrite(data());
官方提示:"不要一直去创建style 记得缓存起来 最多创建6W个就挂了"
Critical: 合并单元格
注解方式
@ContentLoopMerge(eachRow = 2)
@ExcelProperty("字符串标题")
private String string;
策略方式
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
EasyExcel.write(fileName, DemoData.class)
.registerWriteHandler(loopMergeStrategy)
.sheet("模板").doWrite(data());
Critical: 图片导出
@Getter
@Setter
@EqualsAndHashCode
@ContentRowHeight(100)
@ColumnWidth(100 / 8)
public class ImageDemoData {
private File file;
private InputStream inputStream;
@ExcelProperty(converter = StringImageConverter.class)
private String string;
private byte[] byteArray;
private URL url;
private WriteCellData<Void> writeCellDataFile;
}
多图嵌入同一单元格:
WriteCellData<Void> writeCellData = new WriteCellData<>();
imageDemoData.setWriteCellDataFile(writeCellData);
writeCellData.setType(CellDataTypeEnum.STRING);
writeCellData.setStringValue("额外的文字");
List<ImageData> imageDataList = new ArrayList<>();
ImageData imageData = new ImageData();
imageDataList.add(imageData);
writeCellData.setImageDataList(imageDataList);
imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));
imageData.setImageType(ImageType.PICTURE_TYPE_PNG);
imageData.setTop(5);
imageData.setRight(40);
imageData.setBottom(5);
imageData.setLeft(5);
官方原文:"图片都会放到内存 暂时没有很好的解法,大量图片的情况下建议…将图片上传到oss…然后直接放链接"
Critical: 超链接/批注/公式/富文本
@Getter
@Setter
@EqualsAndHashCode
public class WriteCellDemoData {
private WriteCellData<String> hyperlink;
private WriteCellData<String> commentData;
private WriteCellData<String> formulaData;
private WriteCellData<String> writeCellStyle;
private WriteCellData<String> richText;
}
超链接示例:
WriteCellData<String> hyperlink = new WriteCellData<>("官方网站");
HyperlinkData hyperlinkData = new HyperlinkData();
hyperlink.setHyperlinkData(hyperlinkData);
hyperlinkData.setAddress("https://github.com/alibaba/easyexcel");
hyperlinkData.setHyperlinkType(HyperlinkType.URL);
writeCellDemoData.setHyperlink(hyperlink);
公式示例:
WriteCellData<String> formula = new WriteCellData<>();
FormulaData formulaData = new FormulaData();
formula.setFormulaData(formulaData);
formulaData.setFormulaValue("REPLACE(123456789,1,1,2)");
富文本示例:
WriteCellData<String> richTest = new WriteCellData<>();
richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING);
RichTextStringData richTextStringData = new RichTextStringData();
richTest.setRichTextStringDataValue(richTextStringData);
richTextStringData.setTextString("红色绿色默认");
WriteFont writeFont = new WriteFont();
writeFont.setColor(IndexedColors.RED.getIndex());
richTextStringData.applyFont(0, 2, writeFont);
writeFont = new WriteFont();
writeFont.setColor(IndexedColors.GREEN.getIndex());
richTextStringData.applyFont(2, 4, writeFont);
官方提示:"inMemory 要设置为true,才能支持批注"
Critical: Web 下载
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition",
"attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DownloadData.class)
.sheet("模板").doWrite(data());
}
失败回 JSON:
@GetMapping("downloadFailedUsingJson")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition",
"attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DownloadData.class)
.autoCloseStream(Boolean.FALSE)
.sheet("模板").doWrite(data());
} catch (Exception e) {
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = new HashMap<>();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
Critical: 高级特性
动态表头
@Test
public void dynamicHeadWrite() {
EasyExcel.write(fileName)
.head(head())
.sheet("模板")
.doWrite(data());
}
private List<List<String>> head() {
List<List<String>> list = new ArrayList<>();
List<String> head0 = new ArrayList<>();
head0.add("字符串" + System.currentTimeMillis());
list.add(head0);
return list;
}
自动列宽(不太精确)
EasyExcel.write(fileName, LongestMatchColumnWidthData.class)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.sheet("模板").doWrite(dataLong());
自定义拦截器:单元格下拉框
@Slf4j
public class CustomSheetWriteHandler implements SheetWriteHandler {
@Override
public void afterSheetCreate(SheetWriteHandlerContext context) {
CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0);
DataValidationHelper helper = context.getWriteSheetHolder().getSheet()
.getDataValidationHelper();
DataValidationConstraint constraint =
helper.createExplicitListConstraint(new String[]{"测试1", "测试2"});
DataValidation dataValidation =
helper.createValidation(constraint, cellRangeAddressList);
context.getWriteSheetHolder().getSheet().addValidationData(dataValidation);
}
}
自定义拦截器:超链接
@Slf4j
public class CustomCellWriteHandler implements CellWriteHandler {
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
Cell cell = context.getCell();
if (BooleanUtils.isTrue(context.getHead()) && cell.getColumnIndex() == 0) {
CreationHelper createHelper = context.getWriteSheetHolder()
.getSheet().getWorkbook().getCreationHelper();
Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);
hyperlink.setAddress("https://github.com/alibaba/easyexcel");
cell.setHyperlink(hyperlink);
}
}
}
03 版兼容
EasyExcel.write(fileName, DemoData.class)
.excelType(ExcelTypeEnum.XLS)
.sheet("模板").doWrite(data());
Critical: 百万级写入
@GetMapping("/export/large")
public void exportLarge(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("大数据", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition",
"attachment;filename*=utf-8''" + fileName + ".xlsx");
try (ExcelWriter excelWriter = EasyExcel.write(
response.getOutputStream(), UserExportDTO.class).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet("用户列表").build();
int pageSize = 2000;
int totalPage = (int) Math.ceil((double) totalCount / pageSize);
for (int pageNum = 1; pageNum <= totalPage; pageNum++) {
List<UserExportDTO> pageData = userService.findPage(pageNum, pageSize);
excelWriter.write(pageData, writeSheet);
pageData.clear();
}
}
}
Gotchas(常见错误)
| # | 错误 | 正确做法 |
|---|
| 1 | @ExcelProperty index 和 value 同时用 | 只用一种(index 或 name) |
| 2 | 一次性写入 10 万行 | 改为分批 excelWriter.write() |
| 3 | 每次写入都 new WriteCellStyle | 缓存 style 对象,最多 6W 个 |
| 4 | 用 WriteCellData 没设 setType | 设 setType(CellDataTypeEnum.STRING/...) |
| 5 | 批注没设 inMemory(true) | 批注必须 inMemory(true) |
| 6 | Web 导出先写文件再读入 response | 直接 EasyExcel.write(response.getOutputStream()) |
| 7 | 没关闭 ExcelWriter 写入失败 | try-with-resources 自动 finish() |
| 8 | 导出 CSV | EasyExcel 也支持,但中文编码注意 Charset |
| 9 | 大图片全存内存 | 上传 OSS 后用 URL |
| 10 | autoCloseStream(true) 在失败回 JSON 场景 | 设 autoCloseStream(false) |
| 11 | 跨 sheet 顺序不对 | sheetNo 从 0 开始 |
| 12 | 用 Sheet 写自定义逻辑时未注册拦截器 | 写 WriteHandler 后用 registerWriteHandler() |
Key Classes / Methods
| 类型 | 名称 | 用途 |
|---|
| 入口 | EasyExcel.write(path, head) | 写文件 + 表头类 |
| 入口 | EasyExcel.write(outputStream, head) | 写流 + 表头类 |
| 入口 | EasyExcel.write(path) | 写文件(无表头类,运行时表头) |
| 核心 | ExcelWriter | 多次写入容器 |
| 核心 | WriteSheet | 单个 sheet |
| 核心 | WriteTable | sheet 内的多表 |
| 注解 | @ExcelProperty | 表头/索引/转换器 |
| 注解 | @ExcelIgnore | 忽略字段 |
| 注解 | @DateTimeFormat @NumberFormat | 格式 |
| 注解 | @ColumnWidth @HeadRowHeight @ContentRowHeight | 尺寸 |
| 注解 | @HeadStyle @ContentStyle | 样式 |
| 注解 | @ContentLoopMerge | 合并 |
| 配置 | excludeColumnFiledNames(Set) | 排除列 |
| 配置 | includeColumnFiledNames(Set) | 包含列 |
| 配置 | inMemory(true) | 内存模式(批注必须) |
| 配置 | autoCloseStream(false) | 不自动关流(失败回 JSON) |
| 配置 | excelType(ExcelTypeEnum.XLS) | 03 版兼容 |
| 配置 | registerWriteHandler(handler) | 注册拦截器 |
| 工具 | EasyExcel.writerSheet(0, "name").build() | 创建 sheet |
| 工具 | EasyExcel.writerTable(0).needHead(true).build() | 创建 table |
References