Spring Batch は、大量のデータを「読み込む → 加工する → 書き込む」処理を、決まった件数(チャンク)ごとにまとめてコミットしながら進めるためのフレームワークです。 途中で止まっても、どこまで終わったかを記録しているので、同じパラメーターで再実行すると続きから動きます。

検索で見つかる入門記事の多くは Spring Boot 2〜3(Spring Batch 4〜5)の書き方です。Spring Boot 4 に対応する Spring Batch 6 では、スターターが「DB あり/なし」の2種類に分かれ、chunk() の書き方とパッケージ名が変わりました。 この記事では、仕組みを表と図で整理したうえで、CSV を DB に取り込むジョブを Spring Batch 6 の書き方で全文載せ、5系との違いを対応表にしています。

確認環境:Spring Boot 4.1.1、Spring Batch 6.0.5 の公式リファレンス・移行ガイドで確認(確認日 2026年10月10日)。

全体像:Job・Step・Chunk・Tasklet

Job(memberImportJob)
 ├─ Step 1:cleanupStep(Tasklet) ……… 前回のエラー記録を消す(1回で終わる処理)
 └─ Step 2:importStep(Chunk 100件)… CSV を読む → 加工・検証 → DB に書く
        ├─ ItemReader     1件ずつ読む  ×100回
        ├─ ItemProcessor  1件ずつ加工  ×100回(null を返すとその行は捨てる)
        └─ ItemWriter     100件まとめて書く → コミット → 次の100件へ
用語 意味 例
Job バッチ全体。1つ以上の Step を順に(または条件で分岐して)動かす 会員データの取り込み
Step Job の中の1工程 前処理、取り込み、集計
Chunk 指向の Step 読み込み・加工・書き込みを N 件ずつ繰り返し、N 件ごとにコミットする CSV 10万行を100件ずつ DB へ
Tasklet の Step 1つの処理を実行して終わる(トランザクションで包まれる) 一時テーブルの削除、ファイルの移動
JobParameters 実行ごとに渡すパラメーター。同じ値なら「同じ実行(JobInstance)」とみなされる input.file=...、target.date=2026-10-10
JobRepository 実行の記録(どこまで終わったか)を持つ DB のメタデータテーブル

Chunk と Tasklet の使い分け

Chunk Tasklet
向いている処理 件数の多い「読む→加工→書く」 1回で終わる処理(削除、ファイル操作、ストアド呼び出し)
コミットの単位 N 件ごと Tasklet の1回の実行ごと
途中で止まったとき コミット済みのチャンクの続きから再開できる Step の最初からやり直し
スキップ・リトライ 件単位で設定できる 自分で書く

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Batch入門|Chunk・TaskletをBoot 4で書く」

Spring Boot 4 での依存関係

Spring Boot 4 では、Spring Batch のスターターが2つに分かれました。

スターター 実行の記録 向いている場面
spring-boot-starter-batch-jdbc DB のメタデータテーブルに保存 本番のバッチ(再実行・続きからの再開が必要)
spring-boot-starter-batch メモリだけ(DB を使わない) 使い捨ての処理、試し書き
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-batch-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <scope>runtime</scope>
</dependency>

Spring Boot 3 から上げて spring-boot-starter-batch のままだと、実行の記録が DB に保存されなくなります(Spring Boot 4.0 の移行ガイドにも、DB を使い続けるには spring-boot-starter-batch-jdbc に変える必要があると書かれています)。再実行や続きからの再開を使っているジョブは、JDBC 版のスターターに変えます。

# application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/app
spring.datasource.username=app
spring.datasource.password=secret

# メタデータのテーブル(BATCH_JOB_INSTANCE など)を作る。本番は DBA が作るなら never
spring.batch.jdbc.initialize-schema=always

# Job が複数あるときは、起動時に動かす Job を名前で指定する
spring.batch.job.name=memberImportJob

Spring Boot では @EnableBatchProcessing を付けません。 付けると Spring Boot の自動構成が止まり、起動時のジョブの実行やテーブルの作成が行われなくなります。

完成コード:CSV を DB に取り込むジョブ

入力の CSV と取り込み先のテーブル

id,name,email,joined_on
1001,佐藤 花子,hanako@example.com,2026-04-01
1002,鈴木 一郎,ichiro(at)example.com,2026-04-01
1003, 高橋 健 ,KEN@EXAMPLE.COM,2026-05-10
CREATE TABLE member (
  id        BIGINT PRIMARY KEY,
  name      VARCHAR(50)  NOT NULL,
  email     VARCHAR(255) NOT NULL,
  joined_on DATE         NOT NULL
);

CREATE TABLE member_import_error (
  id      BIGSERIAL PRIMARY KEY,
  message VARCHAR(500) NOT NULL
);

1002 はメールアドレスの形が正しくないので取り込まずに飛ばし(スキップ)、1003 は前後の空白を除き、メールアドレスを小文字にして取り込みます。

データの型

package com.example.batch;

/** CSV の1行(文字列のまま受け取る) */
public record MemberCsv(String id, String name, String email, String joinedOn) {}
package com.example.batch;

import java.time.LocalDate;

/** DB に書き込む形 */
public record Member(long id, String name, String email, LocalDate joinedOn) {}
package com.example.batch;

/** 取り込めない行を表す例外(スキップの対象にする) */
public class InvalidMemberException extends RuntimeException {
    public InvalidMemberException(String message) {
        super(message);
    }
}

Job の設定(Spring Batch 6 の書き方)

package com.example.batch;

import java.time.LocalDate;
import java.util.Set;

import javax.sql.DataSource;

import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy;
import org.springframework.batch.infrastructure.item.ItemProcessor;
import org.springframework.batch.infrastructure.item.database.JdbcBatchItemWriter;
import org.springframework.batch.infrastructure.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.infrastructure.item.file.FlatFileItemReader;
import org.springframework.batch.infrastructure.item.file.FlatFileParseException;
import org.springframework.batch.infrastructure.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.infrastructure.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class MemberImportJobConfig {

    @Bean
    public Job memberImportJob(JobRepository jobRepository, Step cleanupStep, Step importStep) {
        return new JobBuilder("memberImportJob", jobRepository)
                .start(cleanupStep)
                .next(importStep)
                .build();
    }

    /** Step 1:前回のエラー記録を消す(Tasklet) */
    @Bean
    public Step cleanupStep(JobRepository jobRepository, PlatformTransactionManager transactionManager,
                            JdbcTemplate jdbc) {
        return new StepBuilder("cleanupStep", jobRepository)
                .tasklet((contribution, chunkContext) -> {
                    jdbc.update("DELETE FROM member_import_error");
                    return RepeatStatus.FINISHED;          // 1回で終わる
                }, transactionManager)
                .build();
    }

    /** Step 2:CSV を100件ずつ読み、加工して DB に書く(Chunk) */
    @Bean
    public Step importStep(JobRepository jobRepository, PlatformTransactionManager transactionManager,
                           FlatFileItemReader<MemberCsv> memberReader,
                           ItemProcessor<MemberCsv, Member> memberProcessor,
                           JdbcBatchItemWriter<Member> memberWriter) {
        var skippable = Set.<Class<? extends Throwable>>of(
                InvalidMemberException.class, FlatFileParseException.class);
        return new StepBuilder("importStep", jobRepository)
                .<MemberCsv, Member>chunk(100)
                .transactionManager(transactionManager)
                .reader(memberReader)
                .processor(memberProcessor)
                .writer(memberWriter)
                .faultTolerant()
                .skipPolicy(new LimitCheckingExceptionHierarchySkipPolicy(skippable, 10))  // 10件まで飛ばす
                .build();
    }

    /** 読み込み:ファイル名は JobParameters の input.file で受け取る */
    @Bean
    @StepScope
    public FlatFileItemReader<MemberCsv> memberReader(
            @Value("#{jobParameters['input.file']}") String inputFile) {
        return new FlatFileItemReaderBuilder<MemberCsv>()
                .name("memberReader")
                .resource(new FileSystemResource(inputFile))
                .encoding("UTF-8")
                .linesToSkip(1)                            // 見出し行
                .delimited()
                .names("id", "name", "email", "joinedOn")
                .targetType(MemberCsv.class)               // record にそのまま詰める
                .build();
    }

    /** 加工:検証と整形。例外を投げた行はスキップ、null を返した行は捨てる */
    @Bean
    public ItemProcessor<MemberCsv, Member> memberProcessor() {
        return csv -> {
            String email = csv.email().strip().toLowerCase();
            if (!email.matches("[^@\\s]+@[^@\\s]+\\.[^@\\s]+")) {
                throw new InvalidMemberException("メールアドレスの形が正しくありません: id=" + csv.id());
            }
            return new Member(
                    Long.parseLong(csv.id().strip()),
                    csv.name().strip(),
                    email,
                    LocalDate.parse(csv.joinedOn().strip()));
        };
    }

    /** 書き込み:100件をまとめて1回の JDBC バッチで INSERT */
    @Bean
    public JdbcBatchItemWriter<Member> memberWriter(DataSource dataSource) {
        return new JdbcBatchItemWriterBuilder<Member>()
                .dataSource(dataSource)
                .sql("INSERT INTO member (id, name, email, joined_on) "
                   + "VALUES (:id, :name, :email, :joinedOn)")
                .itemSqlParameterSourceProvider(m -> new MapSqlParameterSource()
                        .addValue("id", m.id())
                        .addValue("name", m.name())
                        .addValue("email", m.email())
                        .addValue("joinedOn", m.joinedOn()))
                .build();
    }
}
部品 ポイント
chunk(100) + .transactionManager(...) Spring Batch 6 の書き方。5系の chunk(100, transactionManager) は6系では非推奨
@StepScope + #{jobParameters['input.file']} Step の実行時に Bean を作るので、実行ごとのパラメーターを受け取れる
targetType(MemberCsv.class) CSV の列を record の項目に名前で詰める
ItemProcessor が null を返す その行は書き込まずに捨てる(フィルター)。件数は filter_count に記録される
ItemProcessor が例外を投げる スキップの対象なら飛ばす(process_skip_count)。対象外なら Step が失敗する
JdbcBatchItemWriter チャンクの件数ぶんを JDBC のバッチ更新で1回に送る

実行する

Spring Boot のアプリとして起動すると、Job が自動で実行されます。ジョブのパラメーターは -- を付けずに渡します(-- 付きは Spring の設定値として扱われ、ジョブには渡りません)。

java -jar member-batch.jar input.file=/data/members.csv target.date=2026-10-10
# 設定値とジョブのパラメーターを一緒に渡す例(ジョブに渡るのは input.file だけ)
java -jar member-batch.jar --spring.profiles.active=prod input.file=/data/members.csv
やりたいこと 方法
起動時に自動で動かさない(Web アプリから起動するなど) spring.batch.job.enabled=false
Job が複数ある spring.batch.job.name=memberImportJob で1つ指定
コードから起動する JobOperator を注入して start(job, jobParameters)(6系では JobLauncher の役割も JobOperator が持つ)

再実行と JobParameters:同じパラメーターでは2回動かない

Spring Batch は、名前とパラメーターが同じ実行を「同じ JobInstance」とみなします。

前回の結果 同じパラメーターで実行すると
COMPLETED(成功) 実行されない(JobInstanceAlreadyCompleteException)。もう一度動かすならパラメーターを変える
FAILED(失敗) 続きから再開する。Chunk の Step はコミット済みのチャンクの次から読む
STARTED のまま(プロセスが強制終了した) そのままでは再実行できない。状態を確認して FAILED にしてから再実行する(6系では JobOperator に失敗した実行を回復する recover が追加された)

毎日動かすジョブは、target.date=2026-10-10 のように処理対象を表すパラメーターを付けると、「その日の分は1回だけ」「FAILED の日だけやり直す」が自然に表せます。

RunIdIncrementer で毎回 run.id を増やす方法もありますが、Spring Batch 6 では、インクリメンターを付けたジョブに利用者が渡した追加のパラメーターは警告付きで無視されます(移行ガイドより)。ファイル名などをパラメーターで渡すジョブでは、インクリメンターに頼らず、識別できるパラメーターを自分で渡します。

再実行するときは、識別に使うパラメーターも、識別に使わないパラメーターも、すべて前回と同じものを指定し直します(前回の値は引き継がれません)。

メタデータのテーブル:どこまで終わったかを見る

JDBC 版では、次のテーブルに実行の記録が残ります。

テーブル 中身
BATCH_JOB_INSTANCE Job の名前とパラメーターの組み合わせ(JobInstance)
BATCH_JOB_EXECUTION 実行1回ごとの開始・終了時刻、状態(COMPLETED・FAILED など)
BATCH_JOB_EXECUTION_PARAMS 実行に渡したパラメーター
BATCH_STEP_EXECUTION Step ごとの件数(読み・書き・スキップ・コミット・ロールバック)と状態
BATCH_JOB_EXECUTION_CONTEXT/BATCH_STEP_EXECUTION_CONTEXT 再開に使う情報(何行目まで読んだか など)
-- 直近の実行と状態
SELECT ji.job_name, je.job_execution_id, je.status, je.exit_code, je.start_time, je.end_time
FROM batch_job_execution je
JOIN batch_job_instance ji ON ji.job_instance_id = je.job_instance_id
ORDER BY je.job_execution_id DESC
LIMIT 10;

-- Step ごとの件数(1回の実行ぶん)
SELECT step_name, status, read_count, filter_count, write_count,
       read_skip_count, process_skip_count, write_skip_count,
       commit_count, rollback_count
FROM batch_step_execution
WHERE job_execution_id = 42
ORDER BY step_execution_id;

上の CSV(3行)を取り込むと、importStep には「読み3件・書き2件・加工でのスキップ1件」が記録されます(スキップが起きたチャンクでは、ロールバックとやり直しの回数も記録されます)。運用では、この件数を見て「読んだ件数=書いた件数+捨てた件数+スキップ件数」になっているかを確かめます。

スキップとリトライ(Spring Batch 6)

設定 書き方 意味
スキップ .faultTolerant().skipPolicy(new LimitCheckingExceptionHierarchySkipPolicy(例外の集合, 上限)) 指定した例外の行を飛ばす。上限を超えた次の例外で Step が失敗する(上限10なら11件目で失敗)
リトライ .faultTolerant().retryPolicy(RetryPolicy.builder().maxRetries(3).includes(例外の集合).build()) 一時的なエラー(デッドロックなど)の行を最大3回やり直す
飛ばした行の記録 SkipListener を実装して .listener(...) 飛ばした行と理由をエラーテーブルやログに残す

Spring Batch 6 のリトライは、Spring Retry ライブラリではなく Spring Framework 7 のリトライ機能(org.springframework.core.retry.RetryPolicy) を使うように変わりました。

RetryPolicy retryPolicy = RetryPolicy.builder()
        .maxRetries(3)
        .includes(Set.of(DeadlockLoserDataAccessException.class))
        .build();

return new StepBuilder("importStep", jobRepository)
        .<MemberCsv, Member>chunk(100)
        .transactionManager(transactionManager)
        .reader(memberReader)
        .processor(memberProcessor)
        .writer(memberWriter)
        .faultTolerant()
        .retryPolicy(retryPolicy)
        .build();

Spring Batch 5 から 6 で変わったところ

項目 5系(Spring Boot 3) 6系(Spring Boot 4)
スターター spring-boot-starter-batch(DB に記録) spring-boot-starter-batch-jdbc(DB に記録)/spring-boot-starter-batch(メモリのみ)
Chunk の書き方 .chunk(100, transactionManager) .chunk(100).transactionManager(transactionManager)(旧形式は非推奨として6系の間は残る)
Job・JobExecution などのパッケージ org.springframework.batch.core org.springframework.batch.core.job
Step・StepExecution・StepContribution org.springframework.batch.core org.springframework.batch.core.step
JobParameters など org.springframework.batch.core org.springframework.batch.core.job.parameters
リスナーのインターフェース org.springframework.batch.core org.springframework.batch.core.listener
ItemReader・FlatFileItemReader などの部品 org.springframework.batch.item.* org.springframework.batch.infrastructure.item.*
RepeatStatus org.springframework.batch.repeat org.springframework.batch.infrastructure.repeat
ジョブの起動 JobLauncher JobOperator(JobLauncher を継承。JobLauncher は非推奨)
JobExplorer 別の Bean JobRepository が JobExplorer を継承
@EnableBatchProcessing の DB 設定 @EnableBatchProcessing(dataSourceRef = ...) @EnableJdbcJobRepository(dataSourceRef = ...) に分離
DefaultBatchConfiguration の既定 DB に記録 メモリのみ(DB に記録するなら JdbcDefaultBatchConfiguration)
リトライ Spring Retry Spring Framework 7 のリトライ
コマンドラインの起動クラス CommandLineJobRunner CommandLineJobOperator
5系で開始して FAILED のジョブの再開 ― 6系では再開できない(パラメーターの保存形式が変わったため)。移行前に終わらせておく

移行で特に気をつけるのは2点です。1つは、スターターを JDBC 版に替えないと実行の記録が DB に残らなくなること。もう1つは、5系で FAILED のまま残っているジョブを6系では再開できないことです。上げる前に、FAILED のジョブを完了させておきます。

よくある質問

Q. チャンクの件数はいくつにすればいいですか。 A. 数十〜数千件が目安で、100〜1000 から始めて計測して決めます。大きいほどコミットの回数が減って速くなりますが、エラーのときにやり直す件数と、1回のトランザクションで使うメモリが増えます。

Q. 処理の途中で例外が出たら、それまでの件はどうなりますか。 A. コミット済みのチャンクは DB に残り、例外が起きたチャンクはロールバックされます。同じパラメーターで再実行すると、そのチャンクから再開します。読み込み側が再開に対応した部品(FlatFileItemReader など)であることが前提です。

Q. Web アプリの中で、画面の操作からバッチを動かしたいです。 A. spring.batch.job.enabled=false で起動時の自動実行を止め、JobOperator を注入して実行します。時間のかかるジョブは、非同期に実行して、状態はメタデータのテーブルから表示します。

Q. cron で毎日動かしたいです。 A. OS の cron やジョブ管理ツールから java -jar app.jar target.date=$(date +%F) ... のように、日付をパラメーターにして起動する形が扱いやすくなります。アプリの中で @Scheduled から JobOperator を呼ぶ方法もありますが、多重起動の防止とサーバーが複数台のときの扱いを別に考える必要があります。

まとめ

  • Job は Step の集まり、Step は Chunk(N件ずつ読む→加工→書く→コミット)か Tasklet(1回の処理)
  • Spring Boot 4 では spring-boot-starter-batch-jdbc を使うと、実行の記録が DB に残り、FAILED のジョブを続きから再開できる
  • Spring Batch 6 の Chunk は chunk(100).transactionManager(tx)。部品のパッケージは org.springframework.batch.infrastructure.* に移った
  • ジョブのパラメーターは -- を付けずに渡し、同じパラメーターで成功済みのジョブは再実行されない。日付などの識別できるパラメーターを付ける
  • スキップは LimitCheckingExceptionHierarchySkipPolicy、リトライは Spring Framework 7 の RetryPolicy で設定する
  • 運用では BATCH_STEP_EXECUTION の件数(読み・書き・スキップ) で取り込みの結果を確かめる

参考・出典

確認日はいずれも 2026年10月10日です。

  • Spring Batch リファレンス(6.0.5)「What's new in Spring Batch 6.0」:https://docs.spring.io/spring-batch/reference/whatsnew.html
  • Spring Batch「Spring Batch 6.0 Migration Guide」:https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-6.0-Migration-Guide
  • Spring Batch リファレンス(6.0.5)「Configuring a Step(Chunk-oriented Processing)」:https://docs.spring.io/spring-batch/reference/step/chunk-oriented-processing/configuring.html
  • Spring Batch リファレンス(6.0.5)「Configuring Skip Logic」「Configuring Retry Logic」:https://docs.spring.io/spring-batch/reference/step/chunk-oriented-processing/configuring-skip.html 、https://docs.spring.io/spring-batch/reference/step/chunk-oriented-processing/retry-logic.html
  • Spring Batch リファレンス(6.0.5)「TaskletStep」:https://docs.spring.io/spring-batch/reference/step/tasklet.html
  • Spring Batch リファレンス「Meta-Data Schema」:https://docs.spring.io/spring-batch/reference/schema-appendix.html
  • Spring Boot リファレンス(4.1.1)「Batch Applications」:https://docs.spring.io/spring-boot/how-to/batch.html
  • Spring Boot「Spring Boot 4.0 Migration Guide」:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Batch入門|Chunk・TaskletをBoot 4で書く」

株式会社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で一緒に開発しながら、 成長を楽しみませんか?