-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Description
Hello Spring Batch team,
Thank you for the great work on Spring Batch 6! I've encountered a timing issue when using the new StepBuilder(JobRepository) constructor from #4858 together with the tasklet() method -TaskletStepBuilder
Bug description
When using StepBuilder(JobRepository) with tasklet(), the step creation fails because AbstractTaskletStepBuilder.build() calls new TaskletStep(getName()) during bean instantiation, but BeanNameAware.setBeanName() isn't called until after instantiation completes.
Environment
- Spring Batch version: 6.0.0-M4
- Java version: 21
Steps to reproduce
@Configuration
public class MyBatchConfig extends DefaultBatchConfiguration {
@Bean
public Step testStep() {
// Using StepBuilder(JobRepository) from #4858
return new StepBuilder(jobRepository)
// Using tasklet(Tasklet) from #4974
.tasklet((contribution, chunkContext) -> {
System.out.println("test");
return RepeatStatus.FINISHED;
})
.build(); // ← Fails here
}
}Expected behavior
According to the Javadoc of StepBuilder(JobRepository):
/**
* Initialize a step builder for a step with the given job repository.
* The name of the step will be set to the bean name by default.
* @param jobRepository the job repository to which the step should report to.
* @since 6.0
*/
public StepBuilder(JobRepository jobRepository) {
super(jobRepository);
}The TaskletStep should be created successfully with the bean name automatically set via BeanNameAware.
Actual behavior
Application fails with:
Caused by: org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.batch.core.step.Step]:
Factory method 'testStep' threw exception with message: Step name must not be null
Root Cause: Call Chain Analysis
The issue occurs in the following call chain:
TaskletStepBuilder.build()(inherited fromAbstractTaskletStepBuilder)
public TaskletStep build() {
// ...
TaskletStep step = new TaskletStep(getName()); // ← getName() returns null!
// ...
}TaskletStepconstructor
@Deprecated(since = "6.0", forRemoval = true)
public TaskletStep(String name) {
super(name); // ← Passes null to AbstractStep
}AbstractStepconstructor
@Deprecated(since = "6.0", forRemoval = true)
public AbstractStep(String name) {
Assert.notNull(name, "Step name must not be null"); // ← FAILS HERE 💥
this.name = name;
}Since the constructor fails during instantiation, setBeanName() never gets a chance to run.
The Issue:
The combination of:
StepBuilder(JobRepository)- expects name to be set later byBeanNameAwareAbstractTaskletStepBuilder.build()- callsnew TaskletStep(getName())immediatelyAbstractStep(String)constructor - validates name during construction
These three components are incompatible because they operate at different points in the bean lifecycle.
Question
Is this a known limitation of the new constructors?
The StepBuilder(JobRepository) constructor from #4858 seems designed to work with BeanNameAware, but AbstractTaskletStepBuilder.build() requires the name during construction, before BeanNameAware can provide it.
Current Workaround
Use the StepBuilder(String, JobRepository) constructor explicitly:
@Bean
public Step testStep() {
return new StepBuilder("testStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("test");
return RepeatStatus.FINISHED;
})
.build();
}Would appreciate any guidance on whether this is a bug or if there's a specific usage pattern I'm missing. Thank you!