Official documentation:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill Official examples:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java Current version:
com.alibaba:easyexcel:4.0.3(Apache-2.0, repository archived, bug fixes only)
This skill focuses on the "fill by pre-made template" scenario. When the user provides an .xlsx template with styles already drawn and asks to fill in data from a database/API (pay slips, contracts, invoices, reports, statistics sheets, etc.), this skill should be used.
Capability Boundaries
✅ Strengths
- Simple object filling (Map / single DTO)
- List filling (template
{.}syntax, one-directional vertical) - Complex filling (content after list →
forceNewRow=true) - Complex filling with large data volume (list must be placed in the last row, subsequent statistics rows appended with
write) - Horizontal multi-column combined filling (
WriteDirectionEnum.HORIZONTAL+FillWrapper) - Template escape character handling (
\{\}) - Spring Boot integration (template files can come from resources/ or OSS)
⚠️ Limitations
- Only supports
.xlsx(07 version), 03 version (.xls) does not support complex/large data volume filling forceNewRow=trueis fully resident in memory, use with caution for large data volumeswithTemplatetemplates are fully loaded into memory; excessively large template files (>tens of MB) will cause OOM- When filling a list with a Map, all list keys must be included, otherwise an NPE is thrown
❌ Out of Scope (scenarios where this skill should not be used; please use other skills instead)
- Template-based programmatic Excel generation(no pre-made template) → not applicable tothis skill, use
easyexcel-write - Reading Excel data → not applicable tothis skill, use
easyexcel-read - CSV file processing → not applicable tothis skill, use commons-csv / OpenCSV on your own
- Word/PPT → not applicable tothis skill, use Apache POI / Apache POI-XML
Data Privacy
This skill does not collect, store, or transmit any user data. All code examples are for local development reference only.
When to use this skill
- The user says "generate Excel by template", "fill Excel template", "export pay slips/contracts/invoices"
- The user provides an
.xlsxtemplate file and asks to write data into variable placeholders - The report has a fixed format and needs to preserve styles/merged cells/functions/charts
- There is a business requirement to "first fill the list, then append total rows/statistics rows"
Quick Start
Typical invocation methods (choose 1 of 4, in increasing order of file size):
// 方案 A:最简单的对象填充(since 2.1.1)
String fileName = "output_simple.xlsx";
EasyExcel.write(fileName).withTemplate("templates/simple.xlsx")
.sheet().doFill(fillData);
// 方案 B:列表填充(一次放入内存)
EasyExcel.write(fileName).withTemplate("templates/list.xlsx")
.sheet().doFill(list);
// 方案 C:列表分次填充(文件缓存,省内存)
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
.withTemplate("templates/list.xlsx").build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
excelWriter.fill(list, writeSheet); // 多次 fill,自动文件缓存
excelWriter.fill(list, writeSheet);
}
// 方案 D:先 fill 列表 + 后 write 追加合计行(推荐:复杂报表 + 大数据量)
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
.withTemplate("templates/complex.xlsx").build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
excelWriter.fill(list, writeSheet); // 1. 填列表
excelWriter.fill(headerMap, writeSheet); // 2. 填头部变量
excelWriter.write(totalListList, writeSheet);// 3. 手动追加合计行
}
Workflow
Step 1. Confirm the scenario — Has the user provided an .xlsx template? Does the template contain {var} {.} {prefix.} placeholders?
Step 2. Assess the data volume — < 10,000 rows: options A/B; > 10,000 rows: options C/D (list must be placed in the last row)
Step 3. Choose the filling mode — single object / list / complex / horizontal multi-column
Step 4. Construct the data — Map / DTO / List; when filling a list with a Map, all keys must be included
Step 5. Execute fill — try-with-resources close ExcelWriter
Step 6. Verify output — open the generated .xlsx Check whether placeholders were replaced and whether there is any residual {.} {}
Critical: Template syntax quick reference
| Template placeholder | Meaning | Java data structure |
|---|---|---|
{name} {number} {date} |
Single-value placeholder | Map.put("name", value) or DTO field |
{.} |
List placeholder (vertical by default) | List<DTO> passed as the first argument of fill as a whole |
{data1.} {data2.} |
Prefixed list (to distinguish multiple lists) | new FillWrapper("data1", list) |
\{ \} |
Escape: when the template really contains the { } character |
—— |
Official text: "{} ordinary variable, {.} list variable. If there are already '{' and '}' special characters, replace them with '\{' and '\}'. The {. prefix can distinguish different lists."
Critical: Complete examples of the 4 fill scenarios
Scenario 1: Simple object fill
DTO:
@Getter
@Setter
@EqualsAndHashCode
public class FillData {
private String name;
private double number;
private Date date;
}
Template(templates/simple.xlsx): the cell contains {name} {number} {date}
Code:
@Test
public void simpleFill() {
String templateFileName = "templates/simple.xlsx";
String fileName = "output_simpleFill_" + System.currentTimeMillis() + ".xlsx";
// 方案 1:根据对象填充
FillData fillData = new FillData();
fillData.setName("张三");
fillData.setNumber(5.2);
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);
// 方案 2:根据 Map 填充
Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("number", 5.2);
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}
Scenario 2: List fill (template {.} placeholder occupies one row)
Template(templates/list.xlsx): a row of cells contains {.} indicates the start of the list
Code:
@Test
public void listFill() {
String templateFileName = "templates/list.xlsx";
String fileName = "output_listFill_" + System.currentTimeMillis() + ".xlsx";
// 方案 1:一次性放入内存
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data());
// 方案 2:分次填充(文件缓存,省内存,推荐)
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
.withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
excelWriter.fill(data(), writeSheet);
excelWriter.fill(data(), writeSheet);
}
}
Official note: "Filling in multiple batches will use file caching"
Scenario 3: Complex fill (there are also total/summary rows after the list)
Scenario characteristics: in the template, {.} there is also non-list content below the list rows (such as "Total: xxx").
@Test
public void complexFill() {
String templateFileName = "templates/complex.xlsx";
String fileName = "output_complexFill_" + System.currentTimeMillis() + ".xlsx";
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
.withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// ⚠️ 关键:forceNewRow=true 才能让 list 后面再追加内容
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(data(), fillConfig, writeSheet);
excelWriter.fill(data(), fillConfig, writeSheet);
Map<String, Object> map = new HashMap<>();
map.put("date", "2019年10月9日13:28:28");
map.put("total", 1000);
excelWriter.fill(map, writeSheet);
}
}
Official text: "In version 02, the template list is not the last row, and there is still data below, so you need to set
forceNewRow=truebut there is a drawback, which is that it will put all data into memory, use with caution" Official text: "If the data volume is large and the list is not the last row, refer to the next one (complexFillWithTable)"
Scenario 4: Complex fill with a large amount of data (list placed in the last row + write append)
Best-practice template design: delete the "total row" placeholder rows after the list in the template, let the list occupy the last row; use write to manually append subsequent "statistics rows".
@Test
public void complexFillWithTable() {
String templateFileName = "templates/complexFillWithTable.xlsx";
String fileName = "output_complexFillWithTable_" + System.currentTimeMillis() + ".xlsx";
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
.withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 1. 写入 list 数据
excelWriter.fill(data(), writeSheet);
excelWriter.fill(data(), writeSheet);
// 2. 写入 list 之前的变量
Map<String, Object> map = new HashMap<>();
map.put("date", "2019年10月9日13:28:28");
excelWriter.fill(map, writeSheet);
// 3. list 后面手动 write 统计行
List<List<String>> totalListList = new ArrayList<>();
List<String> totalList = new ArrayList<>();
totalListList.add(totalList);
totalList.add(null);
totalList.add(null);
totalList.add(null);
totalList.add("统计:1000");
excelWriter.write(totalListList, writeSheet);
}
}
Official text: "Here it is write, don't confuse it with fill"
Scenario 5: Horizontal fill (multiple columns side by side horizontally)
@Test
public void horizontalFill() {
String templateFileName = "templates/horizontal.xlsx";
String fileName = "output_horizontalFill_" + System.currentTimeMillis() + ".xlsx";
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
.withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 横向:列方向展开
FillConfig fillConfig = FillConfig.builder()
.direction(WriteDirectionEnum.HORIZONTAL).build();
excelWriter.fill(data(), fillConfig, writeSheet);
excelWriter.fill(data(), fillConfig, writeSheet);
Map<String, Object> map = new HashMap<>();
map.put("date", "2019年10月9日13:28:28");
excelWriter.fill(map, writeSheet);
}
}
Scenario 6: Multi-list combined fill (fill multiple lists at the same time)
Template: contains {data1.} {data2.} {data3.} three prefixed lists
@Test
public void compositeFill() {
String templateFileName = "templates/composite.xlsx";
String fileName = "output_compositeFill_" + System.currentTimeMillis() + ".xlsx";
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
.withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder()
.direction(WriteDirectionEnum.HORIZONTAL).build();
// ⚠️ 关键:多个 list 必须用 FillWrapper 包裹,prefix 必须与模板占位符前缀一致
excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet);
excelWriter.fill(new FillWrapper("data2", data()), writeSheet); // data2 纵向
excelWriter.fill(new FillWrapper("data3", data()), writeSheet);
Map<String, Object> map = new HashMap<>();
map.put("date", new Date());
excelWriter.fill(map, writeSheet);
}
}
Official text: "Multiple lists must be wrapped with
FillWrapper;{前缀.}prefixes can distinguish different lists; different lists can specify different directions (HORIZONTAL/VERTICAL)"
Critical: Spring Boot integration
1) Place the template in resources/templates/
@Service
public class SalaryFillService {
public void fillSalary(String employeeId, OutputStream out) {
InputStream templateIn = getClass()
.getResourceAsStream("/templates/salary.xlsx");
SalaryDTO data = salaryMapper.findByEmployee(employeeId);
// 模板以流方式加载,避免临时文件
try (ExcelWriter writer = EasyExcel.write(out)
.withTemplate(templateIn).build()) {
WriteSheet sheet = EasyExcel.writerSheet().build();
writer.fill(data, sheet);
}
}
}
2) Controller returns
@GetMapping("/export/salary/{employeeId}")
public void exportSalary(@PathVariable String employeeId,
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");
salaryFillService.fillSalary(employeeId, response.getOutputStream());
}
3) Complex scenario: batch packaging of pay slips
public void batchExportSalary(List<String> employeeIds, OutputStream zipOut) {
try (ZipOutputStream zos = new ZipOutputStream(zipOut);
ExcelWriter writer = EasyExcel.write(/* unused */).build()) { // 实际逐个写入临时文件
for (String id : employeeIds) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
fillSalary(id, baos);
ZipEntry entry = new ZipEntry("salary_" + id + ".xlsx");
zos.putNextEntry(entry);
zos.write(baos.toByteArray());
zos.closeEntry();
}
}
}
Critical: Business template design specifications
| Business scenario | Recommended template design | Key code |
|---|---|---|
| Pay slip | Single object {name} {base} {bonus} {total} |
Simple fill solution 1 |
| Employee contract | Header {company} {name} {id} + list {items.} |
Simple + list |
| Statement | Header {date} {account} + List {details.} + Footer "Total" |
complexFillWithTable (fill the list first, then write the total row) |
| Financial Report | Horizontal multiple columns {months.} + Vertical {rows.} |
compositeFill (multiple FillWrapper) |
| Invoice | Single object + functions =SUM() |
Simple fill (formulas preset by the template) |
| Product List | List {products.} + Image |
List + Image (images need to be read as byte[]) |
| Summary Table (by month) | Header {year} + List {data1.}(horizontal multi-column expansion) |
horizontalFill |
Gotchas (common mistakes)
| # | Mistake | Correct approach |
|---|---|---|
| 1 | Map fill list misses some keys | When filling a Map, all list keys must be put (value can be null) |
| 2 | Template has { } character not escaped |
Use \{ \} to escape |
| 3 | Complex fill forgets forceNewRow=true |
Must be set when there is content below the list |
| 4 | forceNewRow=true used on millions of records |
Switch to complexFillWithTable (put the list on the last row) |
| 5 | 03 version (.xls) for complex fill |
Switch to 07 version (.xlsx) |
| 6 | Appending a total row after the list uses fill |
Use write("This is write, don't confuse it with fill") |
| 7 | Horizontal multiple columns forgot FillWrapper |
Multiple lists must use new FillWrapper("prefix", list) |
| 8 | Horizontal multiple columns direction not configured | Must FillConfig.builder().direction(HORIZONTAL) |
| 9 | Template file withTemplate(InputStream) not closed after use |
Template streams are cached, close with try-with-resources |
| 10 | When closing ExcelWriter missing finish() |
Use try-with-resources so that ExcelWriter.close() triggers finish |
| 11 | Template file > tens of MB | Switch to multiple small templates filled in batches |
| 12 | Template variable written incorrectly (case / missing { }) |
Template variable names must exactly match the Map key / DTO field |
Key Classes / Methods
| Type | Name | Purpose |
|---|---|---|
| Entry | EasyExcel.write(path).withTemplate(template) |
Specify the template path or stream |
| Entry | EasyExcel.write(OutputStream).withTemplate(stream) |
Web output stream scenario |
| Core | ExcelWriter |
Container for multiple fills, requires try-with-resources |
| Core | WriteSheet |
Single sheet configuration |
| Config | FillConfig.builder().forceNewRow(true) |
Complex fill (content after the list) |
| Config | FillConfig.builder().direction(HORIZONTAL) |
Horizontal fill |
| Wrapper | FillWrapper("prefix", list) |
Multi-list grouping, prefix matches template {prefix.} |
| Utility | EasyExcel.writerSheet().build() |
Create a default WriteSheet |
| Utility | EasyExcel.writerSheet(0, "sheet名").build() |
Specify sheetNo and name |
References
- Official Documentation:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill
- Official Examples:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java
- Project Homepage:https://easyexcel.opensource.alibaba.com/
- GitHub:https://github.com/alibaba/easyexcel
- API Reference:https://easyexcel.opensource.alibaba.com/docs/current/api/
- references/api-reference.md — Complete API and annotation index
- references/template-design.md — Template design specifications and real-world business templates
- references/business-scenarios.md — Real-world cases such as payslips/contracts/statements/invoices
- examples/real-cases.md — Complete copyable code for 6 fill modes
- examples/spring-boot-integration.md — Complete Spring Boot integration example