-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
Background
I understand the design intent behind ResourcelessJobRepository, based on the following issue:
However, I believe there are a few targeted enhancements that would make this class much more useful in tests, without violating the original design goals.
In a single application context, ResourcelessJobRepository cannot run the same job multiple times. This limitation is acceptable as long as users understand it, but in tests it can become a constraint.
For example, the following test runs the same job with different JobParameters using JobOperatorTestUtils, but cannot rely on ResourcelessJobRepository:
@SpringBootTest("spring.batch.job.enabled=false")
@SpringBatchTest
class HelloParamJobTest {
@Autowired
JobOperatorTestUtils testUtils;
@BeforeEach
void prepareTestUtils(@Autowired @Qualifier("helloParamJob") Job helloParamJob) {
testUtils.setJob(helloParamJob);
}
@Test
void startJob() throws Exception {
JobParameters params = testUtils.getUniqueJobParametersBuilder()
.addLocalDate("helloDate", LocalDate.of(2025, 7, 28))
.toJobParameters();
JobExecution execution = testUtils.startJob(params);
assertThat(execution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
}
@Test
void startJobWithInvalidJobParameters() {
JobParameters params = testUtils.getUniqueJobParametersBuilder()
.addLocalDate("goodDate", LocalDate.of(2025, 7, 28))
.toJobParameters();
assertThatExceptionOfType(InvalidJobParametersException.class)
.isThrownBy(() -> testUtils.startJob(params))
.withMessageContaining("do not contain required keys: [helloDate]");
}
}Known solutions
I am aware of several existing workarounds for this limitation:
- Register
ResourcelessJobRepositoryas a prototype-scoped bean. - Use an in-memory database together with a JDBC-based
JobRepository. - Use
@DirtiesContext(or similar) to refresh theApplicationContextbefore each test.
However, all of these come with trade-offs, such as:
- Additional complexity in object wiring and dependencies.
- Extra configuration overhead.
- Slower test execution due to frequent context refreshes.
In particular, as mentioned in this comment:
calls to ResourcelessJobRepository#getJobInstance(String, JobParameters) have caused test scenarios that worked well with Spring Batch v5.2 to become impossible when upgrading to v6.0.
In such cases, users may see an error like:
Message: A job instance already exists and is complete for identifying parameters={JobParameter{name='batch.random', value=4546055881725385948}
This can be confusing because the job name and/or JobParameters are actually different, yet the repository still resolves them to the same JobInstance. This also feels misaligned with Spring Batch's conceptual model, where a JobInstance is uniquely identified by a job name and its JobParameters.
Proposed enhancements
To address these issues while preserving the original design, I would like to propose the following enhancements to ResourcelessJobRepository:
1. Filter return values based on job name, IDs, and parameters
For the methods below, compare the incoming arguments (jobName, instanceId, executionId, JobParameters, etc.) with the values held in the internal JobInstance and JobExecution fields, and filter return values accordingly:
getJobInstances(String jobName, int start, int count)findJobInstances(String jobName)getJobInstance(long instanceId)getLastJobInstance(String jobName)getJobInstance(String jobName, JobParameters jobParameters)getJobInstanceCount(String jobName)getJobExecution(long executionId)getLastJobExecution(String jobName, JobParameters jobParameters)getLastJobExecution(JobInstance jobInstance)getJobExecutions(JobInstance jobInstance)
Some methods already have comments like // FIXME should return null if the id is not matching, which suggest that this kind of filtering was already considered. Even if only a subset of these methods were updated, the observable behavior might be acceptable in practice. However, for conceptual consistency and to future-proof the implementation, I believe it would be better to have a systematic comparison of jobName, jobInstanceId, etc., across the class.
2. Add methods to delete the current JobInstance and JobExecution
Add the ability to drop the currently held JobInstance and JobExecution from ResourcelessJobRepository:
deleteJobInstance(JobInstance jobInstance)deleteJobExecution(JobExecution jobExecution)
If these methods are introduced, tests could intentionally delete the just-run JobInstance or JobExecution to reuse the same ResourcelessJobRepository instance in a more flexible way. For example:
@Autowired
JobOperatorTestUtils testUtils;
@Autowired
JobRepository repository;
@BeforeEach
void prepareTestUtils(@Autowired @Qualifier("helloParamJob") Job helloParamJob) {
testUtils.setJob(helloParamJob);
}
@Test
void startJob() throws Exception {
JobParameters params = testUtils.getUniqueJobParametersBuilder()
.addLocalDate("helloDate", LocalDate.of(2025, 7, 28))
.toJobParameters();
JobExecution execution = testUtils.startJob(params);
assertThat(execution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
// Explicitly clear the current JobInstance for the next test
repository.deleteJobInstance(execution.getJobInstance());
}These two enhancements together would:
- Make the implementation more faithful to the JobRepository interface contract.
- Reduce surprising behavior where different jobs/parameters map to the same
JobInstance.
- Reduce surprising behavior where different jobs/parameters map to the same
- Make
ResourcelessJobRepositorymore useful in test scenarios, especially when upgrading from Spring Batch 5.x to 6.x. - Maintain the original in-memory, single-instance nature of
ResourcelessJobRepository.
I would be happy to open a PR or further refine this proposal based on feedback from the maintainers.