- JAVA
Apache POIの使い方|Excelの読み込み・書き込み・書式・大量出力
Apache POI は、Java から Excel ファイル(.xlsx・.xls)を読み書きするライブラリです。 依存に poi-ooxml を1つ足し、読み込みは WorkbookFactory.create()、書き込みは new XSSFWorkbook() から始めます。
よく検索で見かけるサンプルの多くは「文字列を1セル読む・書く」だけで、実務で困る 数値が 12345.0 で読める、日付が 46305.0 になる、数式の結果が取れない、大量出力でメモリが足りない といった点に触れていません。この記事では、そうした点を含めて、そのまま使えるコード全文と一覧表を1ページにまとめました。
確認環境:Apache POI 5.5.1(2025年11月30日リリース)の公式ドキュメントと Javadoc、Java 21 で確認(確認日 2026年10月10日)。
早見表:やりたいこと別
| やりたいこと | 書き方 |
|---|---|
| ファイルを開く(xls・xlsx 両対応) | Workbook wb = WorkbookFactory.create(new File("in.xlsx")) |
| 新しく作る(xlsx) | Workbook wb = new XSSFWorkbook() |
| 大量の行を書き出す | new SXSSFWorkbook(100)(100行だけメモリに置く) |
| シートを取る・作る | wb.getSheet("社員")/wb.getSheetAt(0)/wb.createSheet("社員") |
| 行・セルを取る(無ければ null) | sheet.getRow(0)、row.getCell(0) |
| 行・セルを作る | sheet.createRow(0)、row.createCell(0) |
| 値を表示どおりの文字列で読む | new DataFormatter().formatCellValue(cell, evaluator) |
| 日付を読む | DateUtil.isCellDateFormatted(cell) なら cell.getLocalDateTimeCellValue() |
| 値を書く | cell.setCellValue("文字" / 123 / LocalDate.now() / true) |
| 数式を書く | cell.setCellFormula("SUM(C2:C10)")(先頭の = は付けない) |
| 保存する | try (var out = new FileOutputStream("out.xlsx")) { wb.write(out); } |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Apache POIの使い方|Excelの読み込み・書き込み・書式・大量出力」
依存関係
<!-- Maven -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.5.1</version>
</dependency>
// Gradle
implementation 'org.apache.poi:poi-ooxml:5.5.1'
| アーティファクト | 中身 | いつ使うか |
|---|---|---|
poi |
共通部分と .xls(HSSF) | .xls だけを扱う |
poi-ooxml |
.xlsx(XSSF・SXSSF)。poi も一緒に入る |
ふつうはこれを1つ入れる |
poi-scratchpad |
.doc・.ppt など Office 97-2003 形式の一部 | Word・PowerPoint の 97-2003 形式 |
クラスの対応
| .xls(Excel 97-2003) | .xlsx(Excel 2007 以降) | 大量出力(.xlsx) | |
|---|---|---|---|
| ブック | HSSFWorkbook |
XSSFWorkbook |
SXSSFWorkbook |
| 最大行数 | 65,536 | 1,048,576 | 1,048,576 |
| 最大列数 | 256(IV 列) | 16,384(XFD 列) | 16,384 |
| セルの書式の上限 | 4,000 | 64,000 | 64,000 |
インターフェース(Workbook・Sheet・Row・Cell)で書いておくと、.xls と .xlsx の両方で同じコードが動きます。
Excel を読み込む(コード全文)
社員一覧(社員番号・氏名・入社日・給与・賞与込みの年収=数式)を読み込み、表示どおりの文字列と、型に合わせた値の両方を取り出します。
import java.io.File;
import java.time.LocalDateTime;
import java.util.Locale;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
public class ReadExcel {
public static void main(String[] args) throws Exception {
try (Workbook wb = WorkbookFactory.create(new File("employees.xlsx"))) { // xls も可
Sheet sheet = wb.getSheetAt(0);
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
DataFormatter formatter = new DataFormatter(Locale.JAPAN);
for (Row row : sheet) { // 中身のある行だけが回る
if (row.getRowNum() == 0) continue; // 見出し行を飛ばす
String id = formatter.formatCellValue(row.getCell(0), evaluator);
String name = formatter.formatCellValue(row.getCell(1), evaluator);
Object hired = value(row.getCell(2), evaluator);
Object total = value(row.getCell(4), evaluator);
System.out.printf("%s %s %s %s%n", id, name, hired, total);
}
}
}
/** セルの種類に合わせて Java の値に変換する(空なら null) */
static Object value(Cell cell, FormulaEvaluator evaluator) {
if (cell == null) return null;
CellType type = cell.getCellType();
if (type == CellType.FORMULA) {
type = evaluator.evaluateFormulaCell(cell); // 数式を計算して結果の種類を得る
}
return switch (type) {
case STRING -> cell.getStringCellValue();
case BOOLEAN -> cell.getBooleanCellValue();
case NUMERIC -> DateUtil.isCellDateFormatted(cell)
? cell.getLocalDateTimeCellValue().toLocalDate() // 日付
: toNumber(cell.getNumericCellValue()); // 数値
case BLANK -> null;
case ERROR -> "#ERR" + cell.getErrorCellValue();
default -> null;
};
}
/** 12345.0 を 12345 に(小数があればそのまま) */
static Number toNumber(double d) {
return (d == Math.rint(d) && Math.abs(d) < Long.MAX_VALUE) ? (Number) (long) d : d;
}
}
読み込みのポイント
| よくある悩み | 原因 | 対処 |
|---|---|---|
社員番号 00123 が 123.0 で読める |
Excel 上は数値、表示形式で0埋めしている | DataFormatter で表示どおりに読む |
日付が 46305.0 で読める |
Excel の日付は「1900年1月1日からの日数」の数値(2026/10/10 は 46305) | DateUtil.isCellDateFormatted で判定して getLocalDateTimeCellValue() |
| 数式のセルで式の文字列や、保存時点の値が読める | getCellFormula() は式、getNumericCellValue() は保存されていた結果 |
FormulaEvaluator で計算し直す |
Cannot get a STRING value from a NUMERIC cell |
セルの種類と違うメソッドで読んだ | getCellType() で分けるか、DataFormatter を使う |
NullPointerException |
空の行・空のセルは null |
null を確認する、または row.getCell(i, MissingCellPolicy.CREATE_NULL_AS_BLANK) |
| 途中の空行で止まる・最終行がずれる | getLastRowNum() は0始まりで、書式だけの行も数える |
for (Row row : sheet) で中身のある行だけ回す |
DataFormatter は「Excel の画面に表示されている文字列」を返すので、CSV に変換するときや、文字列として取り込むときに向いています。合計や比較に使う値は value() のように型で取り出します。
Excel を書き込む(書式つきのコード全文)
見出しの太字・背景色・罫線、日付と金額の表示形式、合計の数式、列幅、ウィンドウ枠の固定、オートフィルターまで設定します。
import java.io.FileOutputStream;
import java.time.LocalDate;
import java.util.List;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class WriteExcel {
record Employee(String id, String name, LocalDate hired, int salary) {}
public static void main(String[] args) throws Exception {
List<Employee> list = List.of(
new Employee("00123", "佐藤 花子", LocalDate.of(2024, 4, 1), 280_000),
new Employee("00124", "鈴木 一郎", LocalDate.of(2025, 10, 1), 300_000),
new Employee("00125", "高橋 健", LocalDate.of(2026, 4, 1), 253_000));
try (Workbook wb = new XSSFWorkbook();
FileOutputStream out = new FileOutputStream("employees.xlsx")) {
Sheet sheet = wb.createSheet("社員");
// 書式はセルごとに作らず、最初に1回だけ作って使い回す
DataFormat fmt = wb.createDataFormat();
Font bold = wb.createFont();
bold.setBold(true);
CellStyle head = wb.createCellStyle();
head.setFont(bold);
head.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
head.setFillPattern(FillPatternType.SOLID_FOREGROUND);
head.setAlignment(HorizontalAlignment.CENTER);
border(head);
CellStyle text = wb.createCellStyle();
text.setDataFormat(fmt.getFormat("@")); // 文字列(0埋めを保つ)
border(text);
CellStyle date = wb.createCellStyle();
date.setDataFormat(fmt.getFormat("yyyy/mm/dd"));
border(date);
CellStyle yen = wb.createCellStyle();
yen.setDataFormat(fmt.getFormat("#,##0"));
border(yen);
String[] titles = {"社員番号", "氏名", "入社日", "月給", "年収(16か月分)"};
Row header = sheet.createRow(0);
for (int i = 0; i < titles.length; i++) {
Cell c = header.createCell(i);
c.setCellValue(titles[i]);
c.setCellStyle(head);
}
int r = 1;
for (Employee e : list) {
Row row = sheet.createRow(r);
cell(row, 0, text).setCellValue(e.id());
cell(row, 1, text).setCellValue(e.name());
cell(row, 2, date).setCellValue(e.hired()); // LocalDate をそのまま渡せる
cell(row, 3, yen).setCellValue(e.salary());
cell(row, 4, yen).setCellFormula("D" + (r + 1) + "*16");
r++;
}
Row sum = sheet.createRow(r);
cell(sum, 2, head).setCellValue("合計");
cell(sum, 3, yen).setCellFormula("SUM(D2:D" + r + ")");
cell(sum, 4, yen).setCellFormula("SUM(E2:E" + r + ")");
int[] widths = {12, 16, 14, 12, 18}; // 文字数の目安
for (int i = 0; i < widths.length; i++) {
sheet.setColumnWidth(i, widths[i] * 256); // 単位は 1/256 文字
}
sheet.createFreezePane(0, 1); // 1行目を固定
sheet.setAutoFilter(new CellRangeAddress(0, r - 1, 0, titles.length - 1));
wb.setForceFormulaRecalculation(true); // 開いたときに数式を計算し直す
wb.write(out);
}
}
static Cell cell(Row row, int col, CellStyle style) {
Cell c = row.createCell(col);
c.setCellStyle(style);
return c;
}
static void border(CellStyle s) {
s.setBorderTop(BorderStyle.THIN);
s.setBorderBottom(BorderStyle.THIN);
s.setBorderLeft(BorderStyle.THIN);
s.setBorderRight(BorderStyle.THIN);
}
}
書き込みのポイント
| 項目 | 書き方 | 注意 |
|---|---|---|
| 表示形式 | wb.createDataFormat().getFormat("yyyy/mm/dd") |
Excel の「ユーザー定義」と同じ書き方。#,##0、0.0%、@(文字列) |
| 日付 | setCellValue(LocalDate) +日付の表示形式 |
表示形式が無いと 46305 のような数値で表示される |
| 数式 | setCellFormula("SUM(D2:D4)") |
先頭に = を付けない。結果は保存時には計算されないので setForceFormulaRecalculation(true) |
| 列幅 | setColumnWidth(列, 文字数 * 256) |
autoSizeColumn は日本語のフォントで幅がずれることがある。大量の行では遅い |
| セルの結合 | sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 4)) |
結合しても値は左上のセルにだけ入れる |
| 書式の作り方 | ブックで1回作って使い回す | セルごとに createCellStyle() すると、上限(xlsx は 64,000)を超えて例外になる |
| フォント | font.setFontName("游ゴシック") |
指定しないとブックの既定のフォント |
大量のデータを書き出す:SXSSF
XSSFWorkbook は全部のセルをメモリに置くため、数十万行を書くとメモリが足りなくなります。SXSSFWorkbook は、直近の行(既定は100行)だけをメモリに置き、それより前の行を一時ファイルに書き出しながら進みます。
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
public class WriteLargeExcel {
public static void main(String[] args) throws Exception {
SXSSFWorkbook wb = new SXSSFWorkbook(100); // メモリに置く行数
wb.setCompressTempFiles(true); // 一時ファイルを gzip で小さくする
try (FileOutputStream out = new FileOutputStream("large.xlsx")) {
SXSSFSheet sheet = wb.createSheet("明細");
sheet.trackAllColumnsForAutoSizing(); // autoSizeColumn を使うなら書く前に
Row header = sheet.createRow(0);
header.createCell(0).setCellValue("番号");
header.createCell(1).setCellValue("金額");
for (int i = 1; i <= 500_000; i++) { // DB のカーソルなどから1行ずつ
Row row = sheet.createRow(i);
row.createCell(0).setCellValue(i);
row.createCell(1).setCellValue(i * 100L);
}
sheet.autoSizeColumn(0);
wb.write(out);
} finally {
wb.dispose(); // 一時ファイルを消す(公式の案内どおり必ず呼ぶ)
wb.close();
}
}
}
| 項目 | XSSF | SXSSF |
|---|---|---|
| メモリ | 全セル分 | 直近 N 行分(既定 100) |
| 書いた行を読み直す | できる | 窓の外に出た行は getRow() が null |
| 一時ファイル | なし | 作られる。dispose() で消す |
| 文字列の持ち方 | 共有文字列テーブル | 既定はセルの中に直接(インライン) |
| 向いている用途 | 書式の細かい帳票、既存ファイルの編集 | 一覧の大量出力 |
結合セル・ハイパーリンク・コメントは SXSSF でもメモリに残るため、大量に使うとメモリを使います。DB から取り出すときも、全件を List に入れずに1行ずつ書くと、Java 側のメモリも抑えられます。
大きなファイルを読む
読み込みには SXSSF のような仕組みがありません。XSSFWorkbook で数十万行のファイルを開くと、ファイルの数倍以上のメモリを使います。行を順に読むだけなら、POI の XSSFReader(SAX 形式のイベント読み込み)や、それを包んだ外部ライブラリでストリーミングして読みます。
Spring Boot で Excel をダウンロードさせる
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletResponse;
@RestController
public class ExcelDownloadController {
@GetMapping("/employees.xlsx")
public void download(HttpServletResponse response) throws Exception {
String fileName = URLEncoder.encode("社員一覧.xlsx", StandardCharsets.UTF_8).replace("+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
try (Workbook wb = new XSSFWorkbook()) {
wb.createSheet("社員").createRow(0).createCell(0).setCellValue("社員番号");
wb.write(response.getOutputStream()); // ファイルを作らずに直接返す
}
}
}
- 日本語のファイル名は
filename*=UTF-8''の形(RFC 6266・RFC 5987)で、URL エンコードして渡します - .xlsx の Content-Type は
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet、.xls はapplication/vnd.ms-excelです - 大量の行なら
SXSSFWorkbookにし、finallyでdispose()します
よくあるエラーと対処
| エラー・症状 | 原因 | 対処 |
|---|---|---|
IllegalStateException: Cannot get a STRING value from a NUMERIC cell |
数値のセルを getStringCellValue() で読んだ |
getCellType() で分ける、DataFormatter を使う |
NotOfficeXmlFileException、OLE2NotOfficeXmlFileException |
xls を XSSFWorkbook、xlsx を HSSFWorkbook で開いた |
WorkbookFactory.create() で開く |
EncryptedDocumentException |
パスワード付きのファイル | WorkbookFactory.create(file, "パスワード") |
Zip bomb detected! |
圧縮率が極端に高いファイル(中身のほとんどが空白など) | 出どころが信頼できるファイルなら ZipSecureFile.setMinInflateRatio(0.001) などで基準を下げる |
IllegalStateException: The maximum number of Cell Styles was exceeded |
セルごとに createCellStyle() している |
書式を最初に作って使い回す |
OutOfMemoryError |
大量の行を XSSFWorkbook で書いた・読んだ |
書くなら SXSSF、読むならイベント読み込み |
起動時に Log4j API could not find a logging provider などの警告 |
POI 5 はログに Log4j API を使う | Spring Boot なら標準のログ設定で log4j-to-slf4j が入っている。単体なら log4j-core か橋渡しの jar を足す |
| 数式のセルが Excel で開くまで 0 のまま | 保存時に計算していない | wb.setForceFormulaRecalculation(true)、または evaluator.evaluateAll() |
NoSuchMethodError、NoClassDefFoundError |
poi と poi-ooxml の版が違う、以前の版の ooxml-schemas が混ざった |
同じ版にそろえる(mvn dependency:tree で確認) |
よくある質問
Q. .xls と .xlsx の両方を読む必要があります。
A. WorkbookFactory.create(file) がファイルの中身を見て HSSFWorkbook か XSSFWorkbook を作るので、コードは Workbook インターフェースで書けば1本で済みます。
Q. 既存の Excel のひな形に値を書き込みたいです。
A. WorkbookFactory.create(new FileInputStream("template.xlsx")) で開き、getRow()/getCell() で場所を取って値を入れ、別のファイル名で保存します。File で開いたブックを同じファイルに書き戻すと、読み込み中のファイルを上書きすることになるため避けます。
Q. CSV と POI のどちらで出力すべきですか。 A. 書式・複数シート・数式が要るなら POI、データの受け渡しだけなら CSV が軽くて速い形です。CSV を Excel で開く場合は、文字コード(BOM 付き UTF-8 など)と、0埋めの番号が数値に変わる点に注意します。
Q. POI はスレッドセーフですか。
A. 1つの Workbook を複数のスレッドから同時に操作するのは避けます。ファイルごとに別の Workbook を作れば、並行して処理できます。
まとめ
- 依存は
poi-ooxmlを1つ。読み込みはWorkbookFactory.create()、書き込みはXSSFWorkbook、大量出力はSXSSFWorkbook - 値は
DataFormatterで表示どおりに、型が要るならgetCellType()で分けて読む。日付はDateUtil.isCellDateFormattedで判定する - 数式は
FormulaEvaluatorで計算して読み、書いたらsetForceFormulaRecalculation(true) - 書式(CellStyle)は最初に作って使い回す。セルごとに作ると上限を超える
- 大量出力は SXSSF。
finallyでdispose()して一時ファイルを消す - Spring Boot のダウンロードは
filename*=UTF-8''で日本語のファイル名を渡す
参考・出典
確認日はいずれも 2026年10月10日です。
- Apache POI「Download」(5.5.1):https://poi.apache.org/download.html
- Apache POI「Busy Developers' Guide to HSSF and XSSF Features」:https://poi.apache.org/components/spreadsheet/quick-guide.html
- Apache POI「How to use the HSSF API(SXSSF を含む)」:https://poi.apache.org/components/spreadsheet/how-to.html
- Apache POI「Formula Evaluation」:https://poi.apache.org/components/spreadsheet/eval.html
- Apache POI Javadoc「DataFormatter」「DateUtil」「SXSSFWorkbook」:https://poi.apache.org/apidocs/5.0/
- Microsoft サポート「Excel の仕様と制限」:https://support.microsoft.com/ja-jp/office/excel-の仕様と制限-1672b34d-7043-467e-8e27-269d656771c3
- RFC 6266「Use of the Content-Disposition Header Field in HTTP」:https://www.rfc-editor.org/rfc/rfc6266
- itsakura「Java Apache POIでエクセルを操作するサンプル」:https://itsakura.com/java-poi
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Apache POIの使い方|Excelの読み込み・書き込み・書式・大量出力」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。