Skip to content

StepBuilder(JobRepository) with tasklet() fails due to timing mismatch between constructor validation and BeanNameAware #5027

@KILL9-NO-MERCY

Description

@KILL9-NO-MERCY

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:

  1. TaskletStepBuilder.build() (inherited from AbstractTaskletStepBuilder)
   public TaskletStep build() {
       // ...
       TaskletStep step = new TaskletStep(getName());  // ← getName() returns null!
       // ...
   }
  1. TaskletStep constructor
   @Deprecated(since = "6.0", forRemoval = true)
   public TaskletStep(String name) {
       super(name);  // ← Passes null to AbstractStep
   }
  1. AbstractStep constructor
   @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 by BeanNameAware
  • AbstractTaskletStepBuilder.build() - calls new TaskletStep(getName()) immediately
  • AbstractStep(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!

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions