はじめに (対象読者・この記事でわかること)

本記事は、Java・Spring Bootを利用した開発経験があるエンジニアを対象とし、Spring Boot アプリケーションに定時実行バッチ(スケジューラ)を組み込む方法を学びたい方を想定しています。この記事を読むことで、@Scheduled アノテーションと Cron 式の書き方、設定ファイルでのタイムゾーン管理、バッチ実行時のロギングやエラーハンドリングのベストプラクティスが理解でき、実際に動作するサンプルプロジェクトを手元で作成できるようになります。業務システムでの定期レポート生成やデータクリーニング、外部 API の定期呼び出しなど、さまざまなユースケースに応用できる基礎が身につくでしょう。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Java 8 以降の基本的な文法と Maven/Gradle の利用経験
- Spring Boot の基礎(@RestControllerapplication.yml の設定ができる)
- 基本的な Linux コマンド操作(Docker があれば尚可)

Spring Boot で定時バッチを走らせる概要と背景

業務アプリケーションでは、一定時間ごとにデータ集計やファイル出力、外部サービスとの同期処理が必要になることが多く、これらは「バッチ」と呼ばれます。従来は OS の cron や Windows タスクスケジューラに依存していましたが、Spring Boot ではアプリケーション内部にスケジューラ機能を組み込めるため、デプロイ環境が統一でき、コードベースでスケジュール管理が可能になります。Spring の @Scheduled アノテーションは、メソッド単位で実行タイミングを指定でき、Cron 式や固定レート、固定ディレイといった柔軟な設定が提供されます。本節では、なぜ Spring Boot がバッチ実装に適しているのか、主要概念(@EnableScheduling、Cron 式、タイムゾーン設定)を簡潔に解説し、実装全体像への導入部として位置付けます。

実践的な手順で作る定時バッチ

以下では、Spring Boot プロジェクトに定時バッチを追加する手順を、コード例と共に段階的に示します。開発環境は Maven を想定していますが、Gradle でも同様です。

ステップ1 プロジェクトの依存関係と設定を追加

まずは spring-boot-starter に加えて、スケジューラ機能を有効にする spring-boot-starter だけで十分ですが、ロギング強化のために spring-boot-starter-logging を明示的に入れます。pom.xml への追記例は次の通りです。

Xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- スケジューラ機能は spring-boot-starter に含まれる --> <!-- 任意で Actuator を入れるとバッチの状態確認が容易 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>

次に、src/main/resources/application.yml にスケジューラの基本設定を記述します。特にタイムゾーンはサーバーのデフォルトと異なる場合があるため、明示的に指定しておくと予期せぬ実行時刻のずれを防げます。

Yaml
spring: task: scheduling: pool: size: 5 # 同時に走らせられるタスク数 thread-name-prefix: scheduler- shutdown: await-termination: true await-termination-period: 30s # タイムゾーンを Asia/Tokyo に固定 jackson: time-zone: Asia/Tokyo

ステップ2 スケジューラの有効化とバッチロジックの実装

Spring Boot アプリケーションのエントリポイントに @EnableScheduling を付与してスケジューラを有効化します。

Java
@SpringBootApplication @EnableScheduling public class BatchApplication { public static void main(String[] args) { SpringApplication.run(BatchApplication.class, args); } }

バッチ本体は、任意の @Component クラスに @Scheduled を付けたメソッドとして実装します。ここでは「毎日 02:30 に売上集計 CSV を生成」する例です。

Java
@Component public class SalesReportBatch { private static final Logger logger = LoggerFactory.getLogger(SalesReportBatch.class); @Scheduled(cron = "0 30 2 * * ?", zone = "Asia/Tokyo") public void generateDailyReport() { logger.info("【定時バッチ】本日分の売上集計を開始します。"); try { // 1. データ取得(例: JPA リポジトリ) List<Sales> sales = salesRepository.findByDate(LocalDate.now().minusDays(1)); // 2. 集計ロジック Map<String, BigDecimal> summary = sales.stream() .collect(Collectors.groupingBy(Sales::getProductId, Collectors.reducing(BigDecimal.ZERO, Sales::getAmount, BigDecimal::add))); // 3. CSV 出力 Path outPath = Paths.get("/var/reports/sales_" + LocalDate.now().minusDays(1) + ".csv"); try (BufferedWriter writer = Files.newBufferedWriter(outPath, StandardCharsets.UTF_8)) { writer.write("商品ID,合計金額\n"); summary.forEach((productId, total) -> { try { writer.write(productId + "," + total + "\n"); } catch (IOException e) { throw new UncheckedIOException(e); } }); } logger.info("【定時バッチ】売上集計CSVが正常に生成されました: {}", outPath); } catch (Exception e) { logger.error("【定時バッチ】売上集計中にエラーが発生しました", e); // 必要に応じてアラートメールや外部通知を実装 } } }

ポイントは以下です。

  1. Cron 式秒 分 時 日 月 曜日 の 6 フィールドで、? は「曜日は不定」と指定する慣例です。
  2. zone 属性で明示的にタイムゾーンを設定すると、サーバーのロケールに左右されません。
  3. 例外処理は必ず捕捉し、ロギングだけでなく外部通知(メールや Slack)に振り分けると運用が楽です。
  4. スレッドプールapplication.ymlspring.task.scheduling.pool.size で調整できます。タスクが増える場合は適宜拡張してください。

ハマった点やエラー解決

実装時に以下のような問題に直面することがあります。

起きた問題 原因 解決策
Failed to configure a TaskScheduler エラー @EnableScheduling が付いていない、または TaskScheduler の Bean が競合している @EnableScheduling がメインクラスに付いているか確認し、カスタム TaskScheduler が不要であれば削除
Cron 式が期待した時刻で実行されない サーバーのタイムゾーンが UTC のまま、zone パラメータ未指定 @Scheduled(cron = "...", zone = "Asia/Tokyo") を必ず記述、もしくは application.ymlspring.task.scheduling.time-zone を設定
バッチ実行時にファイル書き込み権限エラー 出力先ディレクトリのパーミッション不足 Docker コンテナや VM の chmod で書き込み権限を付与、もしくは別ディレクトリに変更
ログが大量に出てコンテナが OOM になる ログレベルが INFO 以上で大量出力 ログレベルを WARN に下げるか、logback-spring.xml でバッチ専用ロガーを分離

解決策の実装例

たとえばタイムゾーンの不一致に悩んだ場合、以下のように application.yml にデフォルトタイムゾーンを設定すると全体に適用されます。

Yaml
spring: jackson: time-zone: Asia/Tokyo task: scheduling: time-zone: Asia/Tokyo

また、エラー時のメール通知は Spring Boot の JavaMailSender を利用してシンプルに実装できます。

Java
@Service public class AlertService { @Autowired private JavaMailSender mailSender; public void sendErrorAlert(String subject, String body) { SimpleMailMessage msg = new SimpleMailMessage(); msg.setTo("ops@example.com"); msg.setSubject(subject); msg.setText(body); mailSender.send(msg); } }

SalesReportBatchcatch (Exception e) 部分で alertService.sendErrorAlert(...) を呼び出すだけで、バッチ失敗時に即座に担当者へ通知できます。

まとめ

本記事では、Spring Boot を用いた定時実行バッチの全体像と実装手順を解説しました。

  • @EnableScheduling と @Scheduled によるシンプルなスケジューラ有効化
  • Cron 式とタイムゾーン の正しい設定で実行時刻のズレを防止
  • ロギング・例外処理・アラート のベストプラクティスで運用性を向上

これらを踏まえることで、業務システムに必須の定期処理をコードベースで安全かつ可搬性高く実装できるようになります。次回は、Spring Batch と比較した大規模データ処理のパターンや、Kubernetes 上でのスケジューラコンテナ化について解説する予定です。

参考資料