easyexcel-fill について
easyexcel-fill は、EasyExcel で用意済みの .xlsx テンプレートにオブジェクトやリストのデータを入力する Java 開発者向けスキルです。複雑・横方向・複数リストの入力方法に加え、メモリや大量データの制約も扱います。
主な機能
- 01単一オブジェクトとリストの入力
- 02複雑な入力と横方向の入力
- 03複数リストの組み合わせ
- 04Spring Boot との連携
- 05テンプレート構文とデータ量の制約
活用シーン
- 01給与明細や契約書のテンプレートへのデータ入力
- 02明細と合計行を含む照合書の作成
- 03横方向のレイアウトを使ったレポート入力
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ full-stack-skills/ java-skills --skill easyexcel-fillインストール後、Claude Code で/easyexcel-fillを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlexamples/business/11-salary.md12-contract.md13-reconciliation.md14-finance-report.md15-invoice.mdexamples/complex-large/05-complex-with-table.mdexamples/complex/04-complex-fill.mdexamples/composite/07-composite.mdexamples/error-handling/18-fail-json.md19-validation.mdexamples/horizontal/06-horizontal.mdexamples/list/02-list-once.md03-list-multi-fill.mdexamples/multi-list/08-mixed-direction.mdexamples/optimization/20-batch.md21-memory.md22-03-vs-07.mdexamples/simple/01-simple-object.mdexamples/spring-boot/09-controller.md10-service.mdexamples/template-style/23-escape.md24-formula.md25-image.mdexamples/web/16-stream-output.md17-async-export.mdreferences/annotations/template-syntax.mdreferences/api/api-reference.mdreferences/comparison/fill-vs-write.mdreferences/design/template-design.md詳細
EasyExcel テンプレートへのデータ入力(Fill)
公式ドキュメント:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill 公式サンプル:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java 現在のバージョン:
com.alibaba:easyexcel:4.0.3(Apache-2.0、リポジトリはアーカイブ済みで、バグ修正のみ)
このスキルは、**「事前に用意したテンプレートへのデータ入力」**のシナリオに特化しています。ユーザーがスタイル設定済みの .xlsx テンプレートを渡し、データベースや API のデータを入力するよう求めている場合(給与明細、契約書、請求書、レポート、集計表など)は、このスキルを使用してください。
対応範囲
✅ 得意なこと
- 単純なオブジェクトへの入力(Map / 単一 DTO)
- リストへの入力(テンプレートの
{.}構文、縦方向のみ) - 複雑な入力(リストの後に内容がある場合 →
forceNewRow=true) - 大量データの複雑な入力(リストは必ず最終行に配置し、後続の集計行は
writeで追加) - 複数列への横方向の入力(
WriteDirectionEnum.HORIZONTAL+FillWrapper) - テンプレートのエスケープ文字の処理(
\{\}) - Spring Boot との統合(テンプレートファイルは resources/ または OSS から取得可能)
⚠️ 制限事項
.xlsx(07 形式)のみサポートします。03 形式(.xls)では複雑な入力や大量データの入力はサポートされませんforceNewRow=trueはデータをすべてメモリに保持するため、データ量が多い場合は注意して使用してくださいwithTemplateはテンプレートをすべてメモリに読み込むため、テンプレートファイルが大きすぎる(数十 MB 超)と OOM が発生します- Map でリストに入力する場合、リストのすべての key を含める必要があります。含まれていない場合は NPE が発生します
❌ 対象外(このスキルを使わないシナリオ。ほかのスキルを使用してください)
- テンプレートを使わないプログラムによる Excel の生成(事前に用意したテンプレートがない場合)→ このスキルは対象外です。
easyexcel-writeを使用してください - Excel データの読み取り → このスキルは対象外です。
easyexcel-readを使用してください - CSV ファイルの処理 → このスキルは対象外です。commons-csv / OpenCSV を使用してください
- Word/PPT → このスキルは対象外です。Apache POI / Apache POI-XML を使用してください
データプライバシー
このスキルは、ユーザーデータを収集、保存、送信しません。すべてのコード例は、ローカル開発での参考用です。
このスキルを使う場面
- ユーザーが「テンプレートから Excel を生成」「Excel テンプレートに入力」「給与明細/契約書/請求書をエクスポート」と依頼した場合
- ユーザーが
.xlsxテンプレートファイルを提供し、変数プレースホルダーにデータを書き込むよう求めた場合 - レポートの形式が固定されており、スタイル/結合セル/関数/グラフを保持する必要がある場合
- 業務上、「まずリストを fill し、その後に合計行/集計行を追加する」要件がある場合
クイックスタート
一般的な呼び出し方法(ファイルサイズの小さい順に 4 つから選択):
// 方案 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. 手动追加合计行
}
ワークフロー
Step 1. シナリオを確認する — ユーザーは .xlsx テンプレートを提供していますか? テンプレートには {var} {.} {prefix.} のプレースホルダーがありますか?
Step 2. データ量を評価する — 1 万行未満:方案 A/B。1 万行超:方案 C/D(リストは必ず最終行に配置)
Step 3. 入力モードを選択する — 単一オブジェクト/リスト/複雑な入力/複数列への横方向の入力
Step 4. データを構築する — Map / DTO / List。Map でリストに入力する場合は、すべての key を含める必要があります
Step 5. 入力を実行する — try-with-resources で ExcelWriter を閉じます
Step 6. 出力を検証する — 生成された .xlsx を開き、プレースホルダーが置換されているか、{.} {} が残っていないか確認します
重要:テンプレート構文の早見表
| テンプレートのプレースホルダー | 意味 | Java データ構造 |
|---|---|---|
{name} {number} {date} | 単一値のプレースホルダー | Map.put("name", value) または DTO フィールド |
{.} | リストのプレースホルダー(デフォルトは縦方向) | List<DTO> 全体を fill の最初の引数として指定 |
{data1.} {data2.} | プレフィックス付きリスト(複数のリストを区別) | new FillWrapper("data1", list) |
\{ \} | エスケープ:テンプレートに { } の文字をそのまま含める場合 | —— |
公式の説明:「{} は通常の変数、{.} は list の変数です。もともと「{」「}」の特殊文字がある場合は、「\{」「\}」に置き換えます。{.前缀は異なる list を区別できます。」
重要:4 種類の入力シナリオの完全な例
シナリオ 1:単純なオブジェクトへの入力
DTO:
@Getter
@Setter
@EqualsAndHashCode
public class FillData {
private String name;
private double number;
private Date date;
}
テンプレート(templates/simple.xlsx):セルに {name} {number} {date} を含めます
コード:
@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);
}
シナリオ 2:リストへの入力(テンプレートの {.} が 1 行を表す)
テンプレート(templates/list.xlsx):1 行のセルに {.} を含め、リストの開始位置を示します
コード:
@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);
}
}
公式のヒント:「複数回に分けて入力すると、ファイルキャッシュが使用されます」
シナリオ 3:複雑な入力(リストの後に合計行/総計行がある)
シナリオの特徴:テンプレート内の {.} のリスト行の下に、リスト以外の内容(「総計: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);
}
}
公式の説明:「02 形式では、テンプレートの list が最終行ではなく、その下にデータがある場合は
forceNewRow=trueを設定する必要があります。ただし、すべてのデータがメモリに読み込まれるという欠点があるため、注意して使用してください」 公式の説明:「データ量が多く、list が最終行ではない場合は、次の例(complexFillWithTable)を参照してください」
シナリオ 4:大量データの複雑な入力(リストを最終行に配置 + write で追加)
推奨するテンプレート設計:テンプレート内のリストの後にある「合計行」のプレースホルダーを削除し、リストを最終行に配置します。後続の「集計行」は write で手動で追加します。
@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);
}
}
公式の説明:「ここでは write を使います。fill と混同しないでください」
シナリオ 5:横方向の入力(複数列を横に並べて配置)
@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);
}
}
シナリオ 6:複数リストを組み合わせた入力(複数の list を同時に入力)
テンプレート:{data1.} {data2.} {data3.} の 3 種類のプレフィックス付きリストを含めます
@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);
}
}
公式の説明:「複数の list は
FillWrapperでラップする必要があります。{前缀.}のプレフィックスで異なる list を区別できます。異なる list に異なる方向(HORIZONTAL/VERTICAL)を指定できます」
重要:Spring Boot との統合
1) テンプレートを 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 の戻り値
@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) 複雑なシナリオ:給与明細の一括パッケージ化
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();
}
}
}
重要:業務テンプレートの設計規則
| 業務シナリオ | 推奨するテンプレート設計 | 重要なコード |
|---|---|---|
| 給与明細 | 単一オブジェクト {name} {base} {bonus} {total} | 単純な入力、方案 1 |
| 従業員契約書 | ヘッダー {company} {name} {id} + リスト {items.} | 単純な入力 + リスト |
| 照合明細書 | ヘッダー {date} {account} + リスト {details.} + 下部に「合計」 | complexFillWithTable(先にリストを fill し、その後に合計行を write) |
| 財務レポート | 複数列への横方向の入力 {months.} + 縦方向 {rows.} | compositeFill(複数の FillWrapper) |
| 請求書 | 単一オブジェクト + 関数 =SUM() | 単純な入力(数式はテンプレートにあらかじめ設定) |
| 商品リスト | リスト {products.} + 画像 | リスト + 画像(画像は byte[] として読み込む必要があります) |
| 集計表(月別) | ヘッダー {year} + リスト {data1.}(横方向に複数列へ展開) | horizontalFill |
Gotchas(常见错误)
| # | 错误 | 正确做法 |
|---|---|---|
| 1 | Map 填充 list 漏掉某些 key | Map 填充时必须 put 所有 list 的 key(可 value=null) |
| 2 | 模板有 { } 字符未转义 | 用 \{ \} 转义 |
| 3 | 复杂填充忘记 forceNewRow=true | list 下面还有内容时必须设置 |
| 4 | forceNewRow=true 用在百万级数据 | 改用 complexFillWithTable(list 放最后一行) |
| 5 | 03 版(.xls)做复杂填充 | 改用 07 版(.xlsx) |
| 6 | list 后追加合计行用 fill | 用 write("这里是write 别和fill 搞错了") |
| 7 | 横向多列忘了 FillWrapper | 多 list 必须用 new FillWrapper("prefix", list) |
| 8 | 横向多列方向忘了配 | 必须 FillConfig.builder().direction(HORIZONTAL) |
| 9 | 模板文件 withTemplate(InputStream) 用完未关 | 模板流是缓存的,try-with-resources 关闭 |
| 10 | 关闭 ExcelWriter 时漏掉 finish() | 用 try-with-resources 让 ExcelWriter.close() 触发 finish |
| 11 | 模板文件 > 几十 MB | 改用多个小模板分批填充 |
| 12 | 模板变量写错(大小写 / 漏 { }) | 模板变量名必须与 Map key / DTO 字段完全一致 |
Key Classes / Methods
| 类型 | 名称 | 用途 |
|---|---|---|
| 入口 | EasyExcel.write(path).withTemplate(template) | 指定模板路径或流 |
| 入口 | EasyExcel.write(OutputStream).withTemplate(stream) | Web 输出流场景 |
| 核心 | ExcelWriter | 多次 fill 的容器,需 try-with-resources |
| 核心 | WriteSheet | 单个 sheet 配置 |
| 配置 | FillConfig.builder().forceNewRow(true) | 复杂填充(list 后还有内容) |
| 配置 | FillConfig.builder().direction(HORIZONTAL) | 横向填充 |
| 包装 | FillWrapper("prefix", list) | 多列表分组,前缀匹配模板 {prefix.} |
| 工具 | EasyExcel.writerSheet().build() | 创建默认 WriteSheet |
| 工具 | EasyExcel.writerSheet(0, "sheet名").build() | 指定 sheetNo 和名称 |
References
- 官方文档:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill
- 官方示例:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java
- 项目主页:https://easyexcel.opensource.alibaba.com/
- GitHub:https://github.com/alibaba/easyexcel
- API 参考:https://easyexcel.opensource.alibaba.com/docs/current/api/
- references/api-reference.md — 完整 API 与注解索引
- references/template-design.md — 模板设计规范与真实业务模板
- references/business-scenarios.md — 工资条/合同/对账单/发票等真实案例
- examples/real-cases.md — 6 种填充模式完整可复制代码
- examples/spring-boot-integration.md — Spring Boot 集成完整示例