モダンJavaバッチ開発:パラメータ駆動ジョブと堅牢な入力検証の実装ハンズオン

はじめに

Spring Batchで開発を行う際、同じロジックを異なるデータや条件で再利用したいケースは頻繁に発生します。
例えば、処理対象のファイル名や日付をジョブ実行の都度変更したい場合です。

このような要求に応えるのが JobParameters です。
JobParameters を使うことで、ジョブの振る舞いを外部から動的に制御でき、再利用性の高いバッチアプリケーションを構築できます。

このハンズオンガイドでは、JobParameters を使ってジョブの動作を外部から制御する方法と、JobParametersValidator を使って入力値を検証しジョブの堅牢性を高める方法を、具体的な実装をハンズオンを通して詳しく解説します。


Spring Batchのパラメータ駆動とジョブの起動・停止の詳細については、以下の記事で詳細に解説していますので、是非ご覧ください。


目次


対象読者

  • Spring Batchの基本的な概念を理解しており、実践的なジョブ開発に興味のあるJava開発者。
  • JobParameters を用いたバッチジョブの動的な制御方法を学びたい方。
  • JobParametersValidator を使った堅牢な入力検証の実装方法を習得したい方。
  • Spring Batch 5.x / Spring Boot 3.x 環境でのモダンなバッチ開発手法に関心のある方。

1. アプリケーションの仕様

1-1. 目的

本ハンズオンを通じて、以下のスキルを習得することを目的とします。

  • JobParameters を使用して、実行時にジョブの挙動を変更する方法を学ぶ。
  • @StepScope を利用して、実行時パラメータをBeanにインジェクションする仕組みを理解する。
  • DefaultJobParametersValidator とカスタムの JobParametersValidator を実装し、ジョブの入力値を堅牢に検証する方法を習得する。
  • Web UIとAPIの両方からパラメータを指定してジョブを実行する方法を実践する。

1-2. 概要

このハンズオンでは、外部から「ファイル名」「処理日」「バージョン」の3つのパラメータを受け取るシンプルな Tasklet ベースのジョブを作成します。

ジョブは起動時に、設定されたバリデーター群(CompositeJobParametersValidator)によってパラメータの妥当性を検証します。

検証を通過すると、Tasklet が実行され、受け取ったパラメータの値をコンソールログに出力します。


1-3. 主要な仕様

  • ジョブ名: jobParametersSampleJob
  • 受け付けるJobParameters:
    • fileName (String, 必須): 処理対象のファイル名。
    • processingDate (String, 必須): 処理基準日。yyyyMMdd 形式の文字列。
    • jobVersion (Double, 任意): ジョブのバージョン。指定がない場合のデフォルト値は 1.0
  • 検証ルール:
    1. 必須チェック: fileNameprocessingDate が存在すること。
    2. 日付フォーマット検証: processingDateyyyyMMdd 形式であり、暦上有効な日付であること(例: 20250230 はエラー)。
    3. ファイル存在チェック: fileName で指定されたファイルが src/main/resources/input/ 配下に存在すること。
    4. バージョン範囲チェック: jobVersion1.0 以上であること。
  • 実行インターフェース:
    • Web UI (http://localhost:8080) からフォーム入力で実行。
    • REST API (GET /launch-job/job-parameters-sample) へクエリパラメータを付与して実行。

1-4. フォルダ構成

このハンズオンで主に扱うファイルは以下の通りです。

.
└── src
    └── main
        ├── java
           └── com
               └── example
                   └── springbatchh2crud
                       ├── config
                          └── JobParametersValidationJobConfig.java
                       ├── controller
                          └── JobLaunchController.java
                       ├── tasklet
                          └── JobParametersSampleTasklet.java
                       └── validator
                           └── CustomJobParametersValidator.java
        └── resources
            └── static
                └── index.html

Spring Batchのサンプルコードのベースを、以下の記事で提供していますので、本記事の実装をする際に、是非ご活用ください。


2. Step 1: JobParameters を受け取るTaskletの実装

まず、ジョブの本体となる Tasklet を作成します。
この Tasklet は、実行時に渡される JobParameters を受け取り、その値をログに出力する簡単な処理を行います。

JobParameters をコンポーネント内で受け取るには、そのコンポーネントが @StepScope として定義されている必要があります。

@StepScope は、Stepの実行ごとにBeanの新しいインスタンスを生成するスコープであり、これにより実行時まで値が確定しない jobParameters をインジェクションできるようになります。

src/main/java/com/example/springbatchh2crud/tasklet/JobParametersSampleTasklet.java

package com.example.springbatchh2crud.tasklet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;

@StepScope // このアノテーションは、Stepの実行ごとに新しいTaskletのインスタンスを作成し、JobParametersを安全にインジェクションできるようにします。
public class JobParametersSampleTasklet implements Tasklet {

    private static final Logger log = LoggerFactory.getLogger(
        JobParametersSampleTasklet.class
    );

    private final String fileName;
    private final String processingDate;
    private final Double jobVersion;

    // コンストラクタインジェクションでJobParametersを受け取る
    public JobParametersSampleTasklet(
        @Value("#{jobParameters['processingDate']}") String processingDate,
        @Value("#{jobParameters['jobVersion'] ?: 1.0}") Double jobVersion // ?: 1.0は、jobVersionパラメータが指定されない場合にデフォルト値1.0を使用するエルビス演算子です。
    ) {
        this.fileName = fileName;
        this.processingDate = processingDate;
        this.jobVersion = jobVersion;
    }

    @Override
    public RepeatStatus execute(
        StepContribution contribution,
        ChunkContext chunkContext
    ) throws Exception {
        log.info("========== JobParametersSampleTasklet start ==========");

        // --- 1. @Value を使用したJobParametersの取得 ---
        // @ValueでインジェクションされたJobParametersの値を使用します。
        log.info("[Way 1: @Value] fileName: {}", fileName);
        log.info("[Way 1: @Value] processingDate: {}", processingDate);
        log.info("[Way 1: @Value] jobVersion: {}", jobVersion);

        // --- 2. JobExecutionからJobParametersを取得 ---
        JobExecution jobExecution = contribution
            .getStepExecution()
            .getJobExecution();
        String fileNameFromJobExec = jobExecution
            .getJobParameters()
            .getString("fileName");
        String processingDateFromJobExec = jobExecution
            .getJobParameters()
            .getString("processingDate");
        Double jobVersionFromJobExec = jobExecution
            .getJobParameters()
            .getDouble("jobVersion");

        log.info("----------------------------------------------------");
        // JobExecutionからJobParametersを取得し、値を使用します。これは別の取得方法です。
        log.info("[Way 2: JobExecution] fileName: {}", fileNameFromJobExec);
        log.info(
            "[Way 2: JobExecution] processingDate: {}",
            processingDateFromJobExec
        );
        log.info("[Way 2: JobExecution] jobVersion: {}", jobVersionFromJobExec);

        log.info("Tasklet executed successfully with received parameters.");
        log.info("=========== JobParametersSampleTasklet end ===========");

        return RepeatStatus.FINISHED;
    }
}

ポイント:

  • @StepScope アノテーションをクラスに付与します。
  • コンストラクタの引数に @Value("#{jobParameters['<キー名>']}") を付けることで、JobParameters の値をインジェクションできます。
  • ?:(エルビス演算子)を使うと、パラメータが存在しない場合のデフォルト値を設定できます(例:jobVersion)。

3. Step 2: JobParametersValidatorによる堅牢な入力検証

ジョブに不正なパラメータが渡されると、予期せぬエラーの原因となります。
これを未然に防ぐのが JobParametersValidator です。


3-1. カスタムバリデーターの実装

必須項目のチェックだけなら DefaultJobParametersValidator で十分ですが、今回はより複雑な検証を行うために CustomJobParametersValidator を実装します。

src/main/java/com/example/springbatchh2crud/validator/CustomJobParametersValidator.java

package com.example.springbatchh2crud.validator;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.JobParametersValidator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

public class CustomJobParametersValidator implements JobParametersValidator { // JobParametersValidatorインターフェースを実装することで、カスタムのパラメータ検証ロジックを提供します。

    private static final Logger log = LoggerFactory.getLogger(
        CustomJobParametersValidator.class
    );

    private static final String FILE_NAME_KEY = "fileName";
    private static final String PROCESSING_DATE_KEY = "processingDate";
    private static final String JOB_VERSION_KEY = "jobVersion";

    @Override
    public void validate(@Nullable JobParameters parameters)
        throws JobParametersInvalidException {
        log.info("Starting custom validation with parameters: {}", parameters);

        if (parameters == null) { // JobParametersがnullの場合、検証をスキップします。
            log.warn("JobParameters is null. Skipping validation.");
            return;
        }

        validateFileName(parameters.getString(FILE_NAME_KEY));
        validateProcessingDate(parameters.getString(PROCESSING_DATE_KEY));
        validateJobVersion(parameters.getDouble(JOB_VERSION_KEY));

        log.info("Custom validation finished successfully.");
    }

    private void validateFileName(String fileName)
        throws JobParametersInvalidException {
        if (!StringUtils.hasText(fileName)) {
            return;
        }
        try {
            ClassPathResource resource = new ClassPathResource(
                "input/" + fileName
            );
            if (!resource.exists()) { // 指定されたファイルがsrc/main/resources/input/ディレクトリに存在するかをチェックします。
                throw new JobParametersInvalidException(
                    "Validation failed: The file specified by 'fileName' (" +
                        fileName +
                        ") does not exist in 'src/main/resources/input/'."
                );
            }
        } catch (Exception e) {
            throw new JobParametersInvalidException(
                "Validation failed: Could not verify the existence of the file '" +
                    fileName +
                    "'. Reason: " +
                    e.getMessage()
            );
        }
    }

    private void validateProcessingDate(String processingDate)
        throws JobParametersInvalidException {
        if (!StringUtils.hasText(processingDate)) {
            return;
        }
        try {
            log.debug(
                "Validating processingDate with DateTimeFormatter: {}",
                processingDate
            );
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern( // yyyyMMdd形式の日付を厳密に解析するためにDateTimeFormatterとResolverStyle.STRICTを使用します。
                "uuuuMMdd"
            ).withResolverStyle(ResolverStyle.STRICT);
            LocalDate.parse(processingDate, formatter);
        } catch (DateTimeParseException e) {
            log.error(
                "DateTimeParseException occurred while validating processingDate.",
                e
            );
            throw new JobParametersInvalidException(
                "Validation failed: The 'processingDate' parameter must be in 'yyyyMMdd' format and be a valid date. Received: '" +
                    processingDate +
                    "'."
            );
        }
    }

    private void validateJobVersion(Double jobVersion)
        throws JobParametersInvalidException {
        if (jobVersion == null) {
            return;
        }
        if (jobVersion < 1.0) { // jobVersionが1.0未満でないかをチェックします。
            throw new JobParametersInvalidException(
                "Validation failed: The 'jobVersion' parameter must be 1.0 or greater. Received: " +
                    jobVersion
            );
        }
    }
}

ポイント:

  • JobParametersValidator インターフェースを実装し、validate メソッドをオーバーライドします。
  • 検証に失敗した場合は、JobParametersInvalidException をスローします。この例外のメッセージが、ジョブ実行失敗時の原因として記録されます。
  • (重要) 日付の検証には、旧来の SimpleDateFormat ではなく、より厳格でスレッドセーフな java.time.DateTimeFormatter を使用します。ResolverStyle.STRICT を指定することで、20250230 のような暦上存在しない日付もエラーとして扱えます。

3-2. 複数のバリデーターを組み合わせる

必須項目チェックを行う DefaultJobParametersValidator と、今回作成した CustomJobParametersValidator を両方適用するために、CompositeJobParametersValidator を使用します。この実装は次のステップで行います。


4. Step 3: ジョブの設定と実行インタフェースの実装

4-1. Job設定クラスの実装

Spring Batch 5.x (Spring Boot 3.x) からは、JobBuilderFactoryStepBuilderFactory を使わない、よりモダンな記法が推奨されています。
ここで TaskletValidator をジョブに組み込みます。

src/main/java/com/example/springbatchh2crud/config/JobParametersValidationJobConfig.java

package com.example.springbatchh2crud.config;

import com.example.springbatchh2crud.tasklet.JobParametersSampleTasklet;
import com.example.springbatchh2crud.validator.CustomJobParametersValidator;
import java.util.Arrays;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.CompositeJobParametersValidator;
import org.springframework.batch.core.job.DefaultJobParametersValidator;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class JobParametersValidationJobConfig {

    @Bean
    @StepScope // @StepScopeを付与することで、JobParametersをTaskletにインジェクションできるようになります。
    public JobParametersSampleTasklet jobParametersSampleTasklet(
        @Value("#{jobParameters['fileName']}") String fileName,
        @Value("#{jobParameters['processingDate']}") String processingDate,
        @Value("#{jobParameters['jobVersion'] ?: 1.0}") Double jobVersion
    ) {
        return new JobParametersSampleTasklet(
            fileName,
            processingDate,
            jobVersion
        );
    }

    @Bean
    public CustomJobParametersValidator customJobParametersValidator() { // カスタムのJobParametersValidatorをBeanとして登録します。
        return new CustomJobParametersValidator();
    }

    @Bean
    public CompositeJobParametersValidator compositeJobParametersValidator() { // 複数のバリデーターを組み合わせて使用するためのCompositeJobParametersValidatorを定義します。
        CompositeJobParametersValidator validator =
            new CompositeJobParametersValidator();
        // 必須パラメータを指定
        DefaultJobParametersValidator defaultJobParametersValidator = // fileNameとprocessingDateを必須パラメータとして設定するDefaultJobParametersValidatorです。
            new DefaultJobParametersValidator(
                new String[] { "fileName", "processingDate" }, // Required keys
                new String[] { "jobVersion", "run.id" } // Optional keys
            );
        defaultJobParametersValidator.afterPropertiesSet();
        // 複数のバリデーターをセット
        validator.setValidators( // DefaultJobParametersValidatorとCustomJobParametersValidatorを登録します。
            Arrays.asList(
                defaultJobParametersValidator,
                customJobParametersValidator()
            )
        );
        return validator;
    }

    @Bean
    public Step jobParametersSampleStep( // JobParametersSampleTaskletを実行するステップを定義します。
        JobRepository jobRepository,
        PlatformTransactionManager transactionManager,
        JobParametersSampleTasklet tasklet
    ) {
        return new StepBuilder("jobParametersSampleStep", jobRepository)
            .tasklet(tasklet, transactionManager)
            .build();
    }

    @Bean
    public Job jobParametersSampleJob(
        JobRepository jobRepository,
        Step jobParametersSampleStep
    ) {
        return new JobBuilder("jobParametersSampleJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .validator(compositeJobParametersValidator()) // ジョブにCompositeJobParametersValidatorを登録し、ジョブ開始前にパラメータ検証を行います。
            .start(jobParametersSampleStep)
            .build();
    }
}

ポイント:

  • JobBuilderStepBuilder のコンストラクタに JobRepository を渡して、直接ジョブやステップを構築します。
  • JobBuilder.validator() メソッドに、作成した CompositeJobParametersValidator を登録します。

4-2. ジョブ実行用インタフェースの実装

ユーザーがブラウザやAPI経由でジョブを実行できるように、@RestController を作成します。

src/main/java/com/example/springbatchh2crud/controller/JobLaunchController.java (抜粋)

@RestController
@RequestMapping("/launch-job")
public class JobLaunchController {
    // ... (DI)

    @GetMapping("/job-parameters-sample")
    public ResponseEntity<Map<String, Object>> launchJobParametersSampleJob(
        @RequestParam String fileName, // HTTPリクエストのクエリパラメータから値を受け取ります。
        @RequestParam String processingDate,
        @RequestParam(required = false) Double jobVersion
    ) {
        Map<String, Object> response = new HashMap<>();
        try {
            JobParametersBuilder paramsBuilder = new JobParametersBuilder();
            paramsBuilder.addString("fileName", fileName);
            paramsBuilder.addString("processingDate", processingDate);
            if (jobVersion != null) {
                paramsBuilder.addDouble("jobVersion", jobVersion);
            }
            // 同じパラメータで再実行できるように、毎回ユニークな値を付与する
            paramsBuilder.addDate("run.id", new Date()); // 同じパラメータでジョブを再実行できるように、ユニークなrun.idを付与します。

            JobParameters jobParameters = paramsBuilder.toJobParameters();
            JobExecution jobExecution = jobLauncher.run(
                jobParametersSampleJob,
                jobParameters
            );
            // ... (成功レスポンスの作成)
            response.put("status", "SUCCESS");
            response.put("message", "Job launched successfully.");
            response.put("jobExecutionId", jobExecution.getId());
            response.put("jobStatus", jobExecution.getStatus().toString());
            return ResponseEntity.ok(response);
        } catch (JobParametersInvalidException e) { // JobParametersの検証エラーを捕捉し、クライアントに適切なエラーメッセージを返します。
            // バリデーションエラーを捕捉
            log.warn("Job parameters validation failed for job '{}': {}",
                jobParametersSampleJob.getName(), e.getMessage());
            response.put("status", "VALIDATION_ERROR");
            response.put("message", "Job parameters validation failed: " + e.getMessage());
            return ResponseEntity.badRequest().body(response);
        } catch (Exception e) {
            // ... (その他のエラー処理)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
}

ポイント:

  • JobParametersInvalidExceptioncatch ブロックで捕捉し、バリデーションエラー発生時にHTTP 400 (Bad Request) とエラーメッセージをクライアントに返すように実装します。

5. 実行方法

5-1. Web UIからの実行

アプリケーションを起動し、ブラウザで http://localhost:8080 にアクセスします。
jobParametersSampleJob のカード内にあるフォームに必要なパラメータを入力し、「Launch jobParametersSampleJob」ボタンをクリックします。

src/main/resources/static/index.html (抜粋)

<div class="job-card">
    <h3>jobParametersSampleJob</h3>
    <p>
        <code>JobParameters</code>
        <code>JobParametersValidator</code> の機能を実演するジョブ。
        パラメータを受け取り、必須チェック、日付形式、数値範囲、ファイル存在の検証を行います。
    </p>
    <form id="jobParamsForm">
        <div class="form-group">
            <label for="fileName">fileName (String)</label>
            <input type="text" id="fileName" name="fileName" value="products.csv" required>
        </div>
        <div class="form-group">
            <label for="processingDate">processingDate (String)</label>
            <input type="text" id="processingDate" name="processingDate" placeholder="yyyyMMdd" required>
        </div>
        <div class="form-group">
            <label for="jobVersion">jobVersion (Double, optional)</label>
            <input type="number" id="jobVersion" name="jobVersion" value="1.0" step="0.1">
        </div>
        <button type="submit">Launch jobParametersSampleJob</button>
    </form>
    <div id="jobParamsResult" class="result-display" style="display: none;"></div>
</div>

実行結果はフォームの下にJSON形式で表示されます。


5-2. APIでの直接実行

ターミナルから curl コマンドを使ってジョブを実行することもできます。

正常実行ケース

curl -X GET "http://localhost:8080/launch-job/job-parameters-sample?fileName=products.csv&processingDate=20251120&jobVersion=1.5"

パラメータ検証エラーケース(必須パラメータ不足)

curl -X GET "http://localhost:8080/launch-job/job-parameters-sample?fileName=products.csv"

パラメータ検証エラーケース(日付フォーマット不正)

curl -X GET "http://localhost:8080/launch-job/job-parameters-sample?fileName=products.csv&processingDate=2025-11-20"

6. 実行と結果解説

6-1. 正常実行ケース

Web UIまたは curl で正しいパラメータ(例: fileName=products.csv, processingDate=20251120)を指定してジョブを実行すると、クライアントには即座に成功のレスポンスが返ります。

クライアント側のレスポンス (curl)

{"jobStatus":"COMPLETED","jobExecutionId":1,"message":"Job launched successfully.","status":"SUCCESS"}

同時に、アプリケーションのコンソールには、バリデーション、ジョブの起動、Taskletの実行、ジョブの完了という一連の流れを示す以下のようなログが出力されます。

サーバー側のログ

2025-11-20T10:15:13.914+09:00  INFO 26513 --- [nio-8080-exec-1] c.e.s.v.CustomJobParametersValidator     : Starting custom validation with parameters: {'fileName':'{...}','run.id':'{...}','jobVersion':'{...}','processingDate':'{...}'}
2025-11-20T10:15:13.915+09:00  INFO 26513 --- [nio-8080-exec-1] c.e.s.v.CustomJobParametersValidator     : Custom validation finished successfully.
2025-11-20T10:15:13.921+09:00  INFO 26513 --- [nio-8080-exec-1] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=jobParametersSampleJob]] launched with the following parameters: [{'fileName':'{...}','run.id':'{...}','jobVersion':'{...}','processingDate':'{...}'}]
2025-11-20T10:15:13.930+09:00  INFO 26513 --- [nio-8080-exec-1] o.s.batch.core.job.SimpleStepHandler     : Executing step: [jobParametersSampleStep]
2025-11-20T10:15:13.942+09:00  INFO 26513 --- [nio-8080-exec-1] c.e.s.t.JobParametersSampleTasklet       : ========== JobParametersSampleTasklet start ==========
2025-11-20T10:15:13.942+09:00  INFO 26513 --- [nio-8080-exec-1] c.e.s.t.JobParametersSampleTasklet       : [Way 1: @Value] fileName: products.csv
2025-11-20T10:15:13.942+09:00  INFO 26513 --- [nio-8080-exec-1] c.e.s.t.JobParametersSampleTasklet       : [Way 1: @Value] processingDate: 20251120
2025-11-20T10:15:13.942+09:00  INFO 26513 --- [nio-8080-exec-1] c.e.s.t.JobParametersSampleTasklet       : [Way 1: @Value] jobVersion: 1.5
2025-11-20T10:15:13.942+09:00  INFO 26513 --- [nio-8080-exec-1] c.e.s.t.JobParametersSampleTasklet       : =========== JobParametersSampleTasklet end ===========
2025-11-20T10:15:13.944+09:00  INFO 26513 --- [nio-8080-exec-1] o.s.batch.core.step.AbstractStep         : Step: [jobParametersSampleStep] executed in 13ms
2025-11-20T10:15:13.946+09:00  INFO 26513 --- [nio-8080-exec-1] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=jobParametersSampleJob]] completed with the following parameters: [...] and the following status: [COMPLETED] in 20ms

ログから、バリデーションが成功し、JobParametersSampleTasklet が渡されたパラメータを正しく受け取って処理を完了したことがわかります。


6-2. パラメータ検証エラーケース

ケース1:必須パラメータ不足

processingDate を指定せずにジョブを実行した場合、エラーはSpring Batchのバリデーター層まで到達しません。その手前のSpring MVCのコントローラー層で、必須リクエストパラメータの不足として検知されます。

クライアント側のレスポンス (curl)

{"timestamp":"2025-11-20T01:15:26.819+00:00","status":400,"error":"Bad Request","path":"/launch-job/job-parameters-sample"}

これはJobLaunchControllerで独自に定義したエラーレスポンスではなく、Spring Bootが提供するデフォルトのエラーレスポンスです。

サーバー側のログ

2025-11-20T10:15:26.815+09:00  WARN 26513 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'processingDate' for method parameter type String is not present]

ログが示す通り、@RequestParamアノテーション(デフォルトでrequired=true)の制約により、リクエストがコントローラーメソッドに到達する時点で却下されていることがわかります。


ケース2:日付フォーマット不正

日付フォーマットを間違えて(例: 2025-11-20)ジョブを実行した場合、リクエストはコントローラーを通過しますが、次に実行されるCustomJobParametersValidatorによってエラーが検知されます。

クライアント側のレスポンス (curl)

{"message":"Job parameters validation failed: Validation failed: The 'processingDate' parameter must be in 'yyyyMMdd' format and be a valid date. Received: '2025-11-20'.","status":"VALIDATION_ERROR"}

こちらはJobLaunchControllercatch (JobParametersInvalidException e)ブロックで処理され、私たちが定義したカスタムエラーメッセージが返却されています。

サーバー側のログ

2025-11-20T10:15:35.098+09:00  INFO 26513 --- [nio-8080-exec-4] c.e.s.v.CustomJobParametersValidator     : Starting custom validation with parameters: ...
2025-11-20T10:15:35.099+09:00 ERROR 26513 --- [nio-8080-exec-4] c.e.s.v.CustomJobParametersValidator     : DateTimeParseException occurred while validating processingDate.

java.time.format.DateTimeParseException: Text '2025-11-20' could not be parsed at index 4
        at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2108) ~[na:na]
        ...
2025-11-20T10:15:35.101+09:00  WARN 26513 --- [nio-8080-exec-4] c.e.s.controller.JobLaunchController     : Job parameters validation failed for job 'jobParametersSampleJob': Validation failed: The 'processingDate' parameter must be in 'yyyyMMdd' format and be a valid date. Received: '2025-11-20'.

CustomJobParametersValidator内でDateTimeParseExceptionが発生し、それがJobParametersInvalidExceptionにラップされてスローされ、最終的にJobLaunchControllerの警告ログとして記録されている、という一連の流れが明確に確認できます。


7. 【コラム】実践的なデバッグプロセス:エラーから学ぶ

今回の実装過程で、いくつかの典型的なエラーに遭遇しました。
これらは初心者開発者が躓く可能性があるポイントなので、その解決過程を記載しておきます。

  1. エラー1:JobBuilderFactory が解決できない
    • 現象: The import ...JobBuilderFactory cannot be resolved というコンパイルエラー。
    • 原因: Spring Boot 3.x / Spring Batch 5.x へのバージョンアップに伴い、JobBuilderFactory が廃止されたため。
    • 解決策: JobBuilder を直接インスタンス化するモダンな記法にコードを修正しました。
  2. エラー2:Bean定義の重複 (BeanDefinitionOverrideException)
    • 現象: jobParametersSampleTasklet というBeanが重複しているという実行時エラー。
    • 原因: Tasklet クラスに @Component(コンポーネントスキャンによる自動登録)を付け、かつ設定クラスで @Bean メソッド(手動登録)を定義したため。
    • 解決策: @StepScope を使うBeanは設定クラス側で管理するのが一般的なため、Tasklet クラスから @Component を削除し、Bean定義を @Bean メソッドに一本化しました。
  3. エラー3:バリデーションが機能しない
    • 現象: 不正な日付文字列を渡してもバリデーションを通過し、ジョブが不正に完了してしまう。
    • 原因: 旧来の SimpleDateFormat クラスが、setLenient(false) を設定しても、特定の状況下で期待通りに厳格な解析を行わなかったため。
    • 解決策: より厳格で信頼性の高い java.time.DateTimeFormatter に置き換えることで、問題を根本的に解決しました。

これらのケースは、「ライブラリのバージョンアップによる変更点を意識すること」「SpringのDIコンテナの仕組みを理解すること」「不安定なAPIを避け、よりモダンで堅牢なAPIを選択すること」の重要性を示しています。


8. まとめ

JobParametersJobParametersValidator は、再利用可能で信頼性の高いバッチジョブを構築するための強力なツールです。

  • JobParameters でジョブの振る舞いを外部から制御し、柔軟性を高める。
  • JobParametersValidator でジョブの入り口を固め、予期せぬデータによるエラーを防ぎ、堅牢性を向上させる。

これらの機能を使いこなすことで、Spring Batchアプリケーションの品質を一段上のレベルに引き上げることができます。


免責事項

本記事の内容は、執筆時点での情報に基づいています。技術情報は常に変化するため、記載された情報が常に最新かつ正確であることを保証するものではありません。本記事の情報を利用したことにより生じるいかなる損害についても、筆者は一切の責任を負いません。ご自身の判断と責任においてご利用ください。


SNSでもご購読できます。

コメントを残す

*