Javaのバッチ処理は、夜間の売上集計や給与計算など、ビジネスシステムを裏で支える重要な技術です。しかし「そもそもバッチ処理って何?」「フレームワークは何を使えばいい?」「エラーが起きたらどう対処する?」と、最初の一歩で迷う方は少なくありません。
この記事では、Javaでバッチ処理を実装する3つの方法(mainメソッド・Spring Batch・Quartz)と、安定稼働に欠かせない5つの設計ポイントをサンプルコード付きで解説します。
私自身、金融機関の大規模システムからスタートアップのWebサービスまで、さまざまなプロジェクトでJavaバッチ処理を開発してきた経験をもとに、現場で本当に役立つノウハウをまとめました。
バッチ処理とは?

Javaでバッチ処理を学ぶ前に、まずは「バッチ処理」そのものについて理解を深めましょう。
バッチ処理とは、ある程度の量のデータを一かたまり(バッチ)にして、一括で処理する方式を指します。身近な例でいうと、銀行の夜間利息計算や、企業の給与計算、ECサイトの売上集計などがバッチ処理にあたります。
ユーザーからのリクエストのたびに処理するのではなく、決められた時間にまとめて実行するのが特徴です。
バッチ処理とリアルタイム処理の違い
バッチ処理とよく比較されるのが「リアルタイム処理」です。この2つの違いを理解すると、バッチ処理の役割がより明確になります。
| 項目 | バッチ処理 | リアルタイム処理 |
| 処理のタイミング | 事前に決められたスケジュール(夜間など) | ユーザーのリクエストに応じて即時 |
| データの量 | 大量 | 少量(1件〜) |
| 処理時間 | ある程度の時間がかかる | 非常に短い(ミリ秒単位) |
| 主な用途 | データ集計、バックアップ、給与計算など | オンライン取引、座席予約、SNSの投稿など |
| システムへの負荷 | 実行時に集中して高くなる | 常に一定の負荷がかかる |
簡単に言えば、「ためて一気に片付ける」のがバッチ処理で、「その都度すぐに片付ける」のがリアルタイム処理とイメージすると分かりやすいでしょう。どちらが良いというわけではなく、用途に応じて使い分けるのが重要です。
Javaでバッチ処理を行うメリット
世の中にはさまざまなプログラミング言語がありますが、その中でもJavaでバッチ処理を開発するメリットは主に3つあります。
Javaでバッチ処理を実装する基本

それでは、具体的にJavaでバッチ処理を実装する方法を見ていきましょう。基本的なアプローチは大きく分けて3つあります。
mainメソッドを使った単純な処理
最もシンプルな方法は、mainメソッドを持つクラスを作成し、その中に処理を記述する方法です。特別なライブラリは不要で、Javaの基本的な知識だけで実装できます。
public class SimpleBatch {
public static void main(String[] args) {
System.out.println("バッチ処理を開始します。");
// ここに実行したい処理を記述する
// 例: ファイルの読み込み、データベースの更新など
try {
// 5秒かかる処理をシミュレート
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("バッチ処理が正常に終了しました。");
}
}このJavaプログラムをコンパイルし、コマンドラインから実行すれば、立派なバッチ処理の完成です。引数(String[] args)を使えば、実行時に処理内容を切り替えることもできます。
スケジューラー(cronやタスクスケジューラ)の利用
mainメソッドで作ったプログラムは、手動で実行する必要がありました。これを自動化するのがスケジューラーです。OSに標準で搭載されている機能で、指定した時間にプログラムを自動で実行してくれます。
- Linux/macOSの場合:
cron - Windowsの場合:
タスクスケジューラ
例えば、Linuxのcronを使って毎日深夜2時に上記のJavaプログラムを実行するには、以下のように設定します。
# crontab -e で編集
0 2 * * * /usr/bin/java -cp /path/to/your/classes com.example.SimpleBatchこのようにOSのスケジューラーと組み合わせることで、定期的なJavaバッチ処理の実行が実現します。
複数ファイルや大量データの処理方法
数百万件のデータや、大量のファイルを一度に処理する場合、何も考えずに実装するとメモリ不足を引き起こす可能性があります。
メモリ不足を避けるには、一度にすべてのデータをメモリに読み込むのではなく、少しずつ読み込んで処理する「ストリーム処理」が基本です。JavaではBufferedReaderやInputStreamを使うことで、ファイルやネットワークからのデータを一行ずつ、あるいは少しずつ読み込めます。
データベースから大量のデータを取得する場合も同様に、一度に全件取得するのではなく、カーソルを使って一件ずつ処理する、あるいはページング処理で一定件数ずつ取得するといった工夫が求められます。
Javaバッチ処理でよく使われるフレームワーク

mainメソッドだけのシンプルな実装は手軽ですが、エラーハンドリングやリトライ、処理の再実行などを自前で作り込むのは大変です。そこで、実務ではバッチ処理に特化したフレームワークを利用するのが一般的です。
Spring Batchの特徴と導入方法
Spring Batchは、Javaのバッチ処理フレームワークにおけるデファクトスタンダード(事実上の標準)です。エンタープライズシステムで求められる複雑な要件に対応するための、さまざまな機能を提供しています。
主な特徴:
Spring Bootを利用すれば、build.gradleやpom.xmlに以下の依存関係を追加するだけで、簡単にSpring Batchを導入できます。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>2026年5月時点の最新はSpring Batch 5.2系(Spring Boot 3.x)およびSpring Batch 6.0系(Spring Boot 4.x)です。本格的なJavaバッチ処理を開発するなら、まず最初に検討すべきフレームワークです。
Quartzによるジョブ管理
Quartzは、ジョブのスケジューリングに特化したライブラリです(2026年時点の最新は2.5系)。「毎週月曜の朝9時に実行」「15分おきに実行」といった、cronよりも柔軟で複雑なスケジュールをJavaコードで管理できます。
Spring Batch自体にはスケジューリング機能がないため、Spring Batchで処理内容を定義し、Quartzで実行タイミングを制御するという組み合わせは非常によく使われます。
シンプルなバッチ処理に便利なツール
Spring Batchは高機能ですが、設定がやや複雑で、ごく簡単な処理には大げさな場合もあります。
数分で終わるような単純なデータ連携処理などであれば、java.util.concurrent.ScheduledExecutorService を使って、Javaアプリケーション内で簡易的なスケジューリングを実装する方法も考えられます。
外部ライブラリを追加したくない、小規模なツールを作成したい、といった場合に検討すると良いでしょう。
jBatch(JSR-352):Java標準のバッチ仕様
jBatch(JSR-352)は、Java EE 7で標準化されたバッチ処理の仕様です。Spring Batchのアーキテクチャを参考に策定されており、Job・Step・Chunk・Batchletといった基本モデルはSpring Batchと似た構造を持っています。
現在はJakarta EE 11に対応したJakarta Batch 2.1として提供されています。Spring Bootプロジェクトであればspring-boot-starter-batchを使うのが自然ですが、Jakarta EEアプリケーションサーバー(WildFly、Open Libertyなど)上で動作させる場合はjBatchが選択肢に入ります。
Javaバッチ処理の設計ポイント5つ

安定して動作するバッチ処理を構築するには、実装だけでなく「設計」が非常に重要です。私がこれまで関わったプロジェクトで共通して感じたのは、バッチ処理の品質は「設計で8割決まる」ということです。
実装自体はシンプルでも、リトライ戦略やログ設計を怠ると、障害時の対応コストが跳ね上がります。ここでは、特に意識すべき5つのポイントを紹介します。
リトライ・エラーハンドリングの考え方
バッチ処理は夜間など、人の目が届かない時間帯に動くことが多いです。そのため、エラーが発生した際に処理を安全に停止し、原因を記録し、可能であれば自動で復旧する仕組みが不可欠です。
- リトライ: データベース接続のタイムアウトなど、一時的な問題で発生したエラーは、少し時間をおいて再試行(リトライ)すれば成功する可能性があります。
- エラーハンドリング: リトライしても解消しないエラーや、データの不整合といった致命的な問題が発生した場合は、処理を中断します。その際、エラー内容、発生箇所、処理中のデータをログに詳しく記録し、開発者が原因を特定できるようにしておく必要があります。
- 再実行性: 失敗した処理を、途中から再実行できる設計(リラン可能)にしておくと、運用負荷を大きく下げられます。Spring Batchは、このあたりの仕組みをうまくサポートしています。
ログ管理と監視の仕組み
「バッチが正常に動いているか?」を把握するためのログ設計も重要です。最低限、以下の情報はログに出力しましょう。
- 処理の開始・終了時刻
- 処理したデータの件数(読み込み件数、書き込み件数)
- エラーが発生した場合は、その内容とスタックトレース
- 主要な処理の経過時間
これらのログをファイルに出力し、DatadogやMackerelといった監視ツールでログを収集・監視することで、異常の早期検知や、処理時間の悪化といったパフォーマンスの問題に気づけるようになります。
パフォーマンス最適化のコツ
大量のデータを扱うバッチ処理では、パフォーマンスが問題になりがちです。処理時間が想定より長引くと、次の日のオンライン業務に影響を及ぼす可能性もあります。
私自身、Spring Batchのchunk sizeを1000に設定した本番バッチでOOM(OutOfMemoryError)を発生させた経験があります。原因調査に2時間かかりましたが、chunk sizeを100に変更するだけで解決しました。この経験から「chunk sizeは小さく始めて、パフォーマンスを見ながら徐々に上げる」のが鉄則だと学びました。
- ボトルネックの特定: 処理が遅い場合、原因はCPU、メモリ、ディスクI/O、ネットワークのどこにあるのかを特定します。
- データベースアクセス: 大量データをループ内で1件ずつ
INSERTやUPDATEするのは非常に非効率です。バルクインサート(一括登録)を利用しましょう。また、SELECT文のパフォーマンスが悪い場合は、インデックスが適切に設定されているか確認が必要です。 - メモリ使用量: 大量のデータをオブジェクトとしてメモリに保持し続けないように注意します。不要になったオブジェクトは速やかに参照を解除し、ガベージコレクションの対象になるようにしましょう。
- 並列処理: 互いに依存しない処理であれば、複数のスレッドで並列実行することで、全体の処理時間を短縮できる場合があります。Spring Batchには、パーティショニングという仕組みで並列処理を簡単にする機能があります。
トランザクション管理とコミット戦略
バッチ処理では、大量データを一度にコミットすると障害時のロールバック範囲が広がり、復旧に時間がかかります。そのため、チャンク単位のコミットが基本戦略です。
Spring Batchではcommit-interval(例: 1000件)を設定することで、一定件数ごとにコミットを実行します。コミット間隔が小さすぎるとDB負荷が増え、大きすぎると障害時の再処理量が増えるため、実務では500〜1000件が一般的な目安です。
なお、Spring Batchと組み合わせて@Transactionalを使う場合、同一クラス内のメソッド呼び出しではトランザクションが効かない点に注意してください(Springのプロキシベースの制約)。私は本番環境でデータ不整合が発生して初めてこの問題に気づきました。バッチのサービス層は必ずクラスを分割し、外部から呼び出す設計にしましょう。
冪等性の確保(再実行しても安全な設計)
冪等性(べきとうせい)とは、同じ処理を何度実行しても結果が変わらない性質のことです。バッチ処理では障害時の再実行や重複実行が避けられないため、冪等な設計が求められます。
具体的な実現方法としては、INSERTの代わりにUPSERT(INSERT ... ON DUPLICATE KEY UPDATE)を使う、処理済みフラグや処理日時カラムで二重処理を防ぐ、といった手法があります。Spring BatchではJobInstanceとJobExecutionの仕組みで再実行を制御できます。
実践例:Javaバッチ処理のサンプルコード3選

ここでは、よくある3つのケースについて、簡単なサンプルコードを交えながら実装イメージを紹介します。
ファイル読み込みと書き込み
CSVファイルを1行ずつ読み込み、特定の条件(例: 2列目の値が "TOKYO")に一致する行だけを別のファイルに書き出すバッチ処理です。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FileBatchExample {
public static void main(String[] args) {
String inputFile = "input.csv";
String outputFile = "output.csv";
try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputFile));
BufferedWriter writer = Files.newBufferedWriter(Paths.get(outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
String[] columns = line.split(",");
if (columns.length >= 2 && "TOKYO".equals(columns[1])) {
writer.write(line);
writer.newLine();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}データベース連携
JDBCを使い、usersテーブルから未処理のユーザー(status = 0)を取得し、処理済み(status = 1)に更新する例です。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class DbBatchExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "user";
String password = "password";
String selectSql = "SELECT id FROM users WHERE status = 0";
String updateSql = "UPDATE users SET status = 1 WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement selectStmt = conn.prepareStatement(selectSql);
PreparedStatement updateStmt = conn.prepareStatement(updateSql)) {
conn.setAutoCommit(false); // トランザクション開始
ResultSet rs = selectStmt.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
// 何らかの処理...
updateStmt.setInt(1, id);
updateStmt.addBatch();
}
updateStmt.executeBatch(); // バッチ更新
conn.commit(); // トランザクション確定
} catch (Exception e) {
// エラー発生時はロールバックする(コードは省略)
e.printStackTrace();
}
}
}APIを利用したデータ取得と保存
外部のAPIからJSON形式で天気情報を取得し、その結果をファイルに保存するバッチ処理です。Java 11から標準搭載されたHttpClientを利用しています(Java 21/25 LTSでも利用可能)。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;
public class ApiBatchExample {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.weather.example.com/tokyo"))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String jsonData = response.body();
// 取得したJSONデータをファイルに保存
Files.writeString(Paths.get("weather.json"), jsonData);
} catch (Exception e) {
e.printStackTrace();
}
}
}Javaバッチ処理のよくある質問(FAQ)
Q. Javaのバッチ処理とリアルタイム処理はどう使い分ける?
大量データの一括処理(売上集計・データ移行・バックアップなど)にはバッチ処理、ユーザーの操作に即座に応答する必要がある処理(オンライン取引・チャットなど)にはリアルタイム処理が適しています。処理の即時性よりもスループットを優先する場面ではバッチ処理を選びましょう。
Q. Spring Batchは小規模プロジェクトでも使うべき?
数百件程度のデータ処理や、数分で終わる単純な処理であれば、mainメソッド+ScheduledExecutorServiceで十分です。一方、リトライ・スキップ・ジョブ管理・再実行制御が必要な場合は、規模に関わらずSpring Batchの導入を検討する価値があります。
Q. バッチ処理が失敗したときの対処法は?
まずログでエラー箇所と原因を特定し、一時的な障害(DB接続タイムアウトなど)であればリトライで復旧します。データ不整合が原因の場合は、該当データを修正した上で冪等性が担保された設計であれば安全に再実行できます。Spring BatchのJobExecutionを使えば、失敗したステップから再開することも可能です。
まとめ
この記事では、Javaのバッチ処理について、基本的な考え方から実装方法、そして実務で役立つ設計のポイントまで幅広く解説しました。
バッチ処理は、Webアプリケーションのように華やかではありませんが、ビジネスシステムを裏で支える非常に重要な技術です。安定したバッチ処理を構築できるスキルは、Javaエンジニアとして大きな強みになります。
Javaバッチ処理の学習ロードマップ
これからJavaのバッチ処理を本格的に学びたい方は、以下のステップで進めるのがおすすめです。
- Javaの基本をマスターする: ファイルI/O、JDBC、例外処理など、基本的なAPIをしっかり理解します。
mainメソッドで簡単な処理を作る: まずはライブラリに頼らず、小さなバッチを自分で作ってみましょう。- Spring Batchを学ぶ: 公式ドキュメントや入門書を参考に、Spring Batchの基本(Job, Step, ItemReader, ItemWriter)を学び、チュートリアルを動かしてみます。
- 設計と運用の知識を深める: エラーハンドリング、ログ設計、監視、パフォーマンスチューニングなど、本記事で紹介した設計のポイントを意識して、より実践的なバッチ処理の構築を目指します。
まずは、Spring BootとSpring Batchを使って、簡単なCSVファイルとデータベースを連携させるバッチ処理を作成してみることをお勧めします。実際に作ってみることで、フレームワークの便利さや、設計で考慮すべき点がより具体的に理解できるはずです。

