How does StepExecutionListener work, and what is the @BeforeStep annotation commonly used for?
answer
- beforeStep / afterStep
- afterStep returns ExitStatus (null = unchanged)
- @BeforeStep on reader -> inject StepExecution -> JobParameters
- must be registered as listener
- StepExecution counts + ExecutionContext
basics
~20 sStepExecutionListener has beforeStep and afterStep callbacks that run around a single step. @BeforeStep is often put on a reader or writer method to grab the StepExecution — and through it the JobParameters — so the component can configure itself.
solid answer
~40 sStepExecutionListener wraps one step: beforeStep(StepExecution) runs before the step's chunk loop starts, and afterStep(StepExecution) runs after it ends and can return an ExitStatus to override the step's outcome (return null to leave it unchanged). The StepExecution gives you read/write/skip counts, the step ExecutionContext, and access to the JobExecution/JobParameters. The most common real-world use of the annotation form is @BeforeStep on a reader, processor, or writer: Spring injects the StepExecution so the component can pull JobParameters or restart data from the ExecutionContext without being a full listener. You register listeners with StepBuilder.listener(...). Because afterStep can rewrite the ExitStatus, it's a hook for flow control — e.g., mark a step as FAILED if zero rows were read, or return a custom ExitStatus that a job flow transition keys on.
code
java · 17 linespublic class FileItemReaderWithStep {
private String inputFile;
@BeforeStep
public void grabParameters(StepExecution stepExecution) {
JobParameters params = stepExecution.getJobParameters();
this.inputFile = params.getString("input.file");
}
}
// A StepExecutionListener that drives flow via ExitStatus
public class EmptyGuardListener implements StepExecutionListener {
@Override
public ExitStatus afterStep(StepExecution se) {
return se.getReadCount() == 0 ? new ExitStatus("NO_DATA") : null; // null = keep
}
}go deeper
Know beforeStep/afterStep run around one step and @BeforeStep can hand a reader the JobParameters.
Know afterStep returns an overriding ExitStatus and that the annotated bean must be registered as a listener.
Use afterStep for conditional flow and guard against masking FAILED; know StepExecution's counters and ExecutionContext.
Reason about @BeforeStep vs @StepScope SpEL for late binding, restartability via the step ExecutionContext, and ExitStatus-driven job orchestration.
## StepExecutionListener A **Step** is one phase of a job; a chunk-oriented step reads/processes/writes items in transactional chunks. `StepExecutionListener` brackets a single step: - `void beforeStep(StepExecution stepExecution)` — before the chunk loop begins. - `ExitStatus afterStep(StepExecution stepExecution)` — after the step finishes (success or failure). Its **return value can override the step's ExitStatus**; return `null` to keep the existing one. `StepExecution` exposes: `getReadCount()`, `getWriteCount()`, `getSkipCount()`, `getCommitCount()`, `getRollbackCount()`, the step-scoped `ExecutionContext`, and `getJobExecution()` (hence `getJobParameters()`). ### afterStep as flow control Because `afterStep` returns an `ExitStatus`, it's a common way to drive conditional job flow: ```java public ExitStatus afterStep(StepExecution se) { return se.getReadCount() == 0 ? new ExitStatus("NO_DATA") : null; } ``` A `.on("NO_DATA").to(...)` transition in the job can then branch. Returning `null` means "don't change it." ## The @BeforeStep idiom Readers/processors/writers frequently need `JobParameters` (e.g., an input filename) or restart state from the `ExecutionContext`, but you don't want them to be full listeners. Spring lets you annotate any method on a step component: - `@BeforeStep void init(StepExecution stepExecution)` — Spring detects the annotation when the component is **registered as a listener** and calls it with the `StepExecution`. Key detail: **the annotated component must also be registered via `StepBuilder.listener(...)`** (or be the reader/writer, which Spring auto-registers as a listener when it detects the annotations). Simply having the annotation on a bean that isn't wired as a listener does nothing. Annotation forms: `@BeforeStep`, `@AfterStep` (may return `ExitStatus`). ## Registration ```java new StepBuilder("step1", jobRepository) .<In, Out>chunk(100, tx) .reader(reader).processor(processor).writer(writer) .listener(stepListener) // interface or @Before/@AfterStep bean .build(); ``` The fluent `listener(Object)` overload inspects the object: if it implements `StepExecutionListener` it's used directly; if it carries the annotations it's wrapped by `StepListenerFactoryBean`. ## Gotchas - `afterStep` runs even when the step FAILED — check status before assuming success. - Returning a new `ExitStatus` from `afterStep` overrides the natural one; accidentally returning a non-null status can mask a real failure. Return `null` unless you mean to change it. - `@BeforeStep` on a bean that isn't registered as a listener is silently ignored. - With `@StepScope` components, `@BeforeStep` is one clean way to grab late-bound values, though `@Value("#{jobParameters['x']}")` is often simpler.
- What happens if afterStep returns a non-null ExitStatus?It replaces the step's ExitStatus. That value then feeds job flow transitions (.on(...).to(...)). Return null to leave the natural status untouched; returning a status carelessly can hide a FAILED step.
- Why put @BeforeStep on a reader instead of injecting @Value job parameters?@BeforeStep gives the whole StepExecution — JobParameters plus the step ExecutionContext (restart data, counts). @Value SpEL is simpler for a single parameter, but @BeforeStep is the way to reach restart state or multiple runtime values.
saying these in an interview costs you the question
- Thinking afterStep only runs on success
- Believing @BeforeStep works without registering the bean as a listener
- Not knowing afterStep can override ExitStatus / thinking its return value is ignored