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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?