skip to content

What is @StepScope in Spring Batch and why would you annotate a reader or writer bean with it?

level: juniorimportance: must knowfreq 68%

answer

  1. New instance per StepExecution, not per context
  2. Enables late binding of #{jobParameters[...]}
  3. Values don't exist at startup, only at step run
  4. Scoped proxy delegates to current step's bean
  5. @JobScope = per JobExecution sibling

basics

~20 s

@StepScope is a custom Spring scope where a new bean instance is created for each step run. You use it so a reader or writer can pick up values (like job parameters) that only exist when the job actually runs, not at startup.

solid answer

~40 s

@StepScope is a Spring Batch custom bean scope ("step") registered by @EnableBatchProcessing. A @StepScope bean is instantiated fresh for every StepExecution rather than once at application-context startup like a normal singleton. Its main purpose is late binding: it lets you inject runtime values such as #{jobParameters['inputFile']} or #{stepExecutionContext['key']} into readers, writers, and processors via SpEL. Those values do not exist when the context is built — they only become available when the step starts — so the bean must be created lazily, per step, for the expressions to resolve. Spring implements this with a scoped proxy: the singleton step configuration injects a proxy, and each method call is delegated to the real instance bound to the current step execution. Without @StepScope, the SpEL would fail or resolve to null at startup.

code

java · 16 lines
java
@Configuration
public class ImportStepConfig {

    @Bean
    @StepScope // new instance per step run -> jobParameters available
    public FlatFileItemReader<Customer> reader(
            @Value("#{jobParameters['inputFile']}") String path) {
        return new FlatFileItemReaderBuilder<Customer>()
            .name("customerReader")
            .resource(new FileSystemResource(path))
            .delimited()
            .names("id", "name")
            .targetType(Customer.class)
            .build();
    }
}

go deeper

for a junior

Know that @StepScope means a fresh bean per step run and that it lets readers/writers use job parameters supplied at launch time.

for a middle

Explain late binding and the SpEL syntax #{jobParameters['x']}, and why the bean can't be a singleton.

for a senior

Add the scoped-proxy mechanism and the @JobScope distinction including which context each can read.

for a principal

Discuss the StepSynchronizationManager/ThreadLocal binding, proxy modes, and failure modes like 'Scope step is not active'.

## The problem it solves Spring's default bean scope is **singleton**: every `@Bean` is created once, eagerly, when the `ApplicationContext` starts up. That is fine for stateless services, but Spring Batch readers/writers frequently need **runtime input** — the file to read, an SQL parameter, a date — that is supplied as **job parameters** when a job is *launched*, which can be long after the context started. If you tried to inject `#{jobParameters['inputFile']}` into a plain singleton bean, the expression would be evaluated at context-refresh time when **no job is running**, so there is no `JobParameters` to read — you get an error or `null`. ## What @StepScope does `@StepScope` marks a bean as living in the custom **`step`** scope. Instead of one instance at startup, Spring creates a **new instance for each `StepExecution`** (each run of that step). Because creation is deferred until the step actually starts, the `JobParameters` and the step's `ExecutionContext` are available, so SpEL like `#{jobParameters['inputFile']}` and `#{stepExecutionContext['partitionRange']}` resolve correctly. This deferred resolution of runtime values into bean definitions is called **late binding**. `@JobScope` is the sibling scope: a new instance **per `JobExecution`**, living for the whole job. Use `@JobScope` for beans that must survive across steps but still need late binding of job parameters; `@StepScope` for step-local beans (and it is the only one that can read `stepExecutionContext`). ## How to use it ```java @Bean @StepScope public FlatFileItemReader<Customer> reader( @Value("#{jobParameters['inputFile']}") String path) { return new FlatFileItemReaderBuilder<Customer>() .name("customerReader") .resource(new FileSystemResource(path)) .delimited().names("id", "name") .targetType(Customer.class) .build(); } ``` The `@Value("#{...}")` SpEL is the late-binding hook; `@StepScope` is what makes it resolvable. ## How it works under the hood (brief) The scopes are registered by `@EnableBatchProcessing` (or Spring Boot auto-config). Behind the scenes each `@StepScope` bean is injected as a **scoped proxy** (`proxyMode = TARGET_CLASS` by default). The singleton `Step` holds the proxy; every method invocation is forwarded to the real bean bound to the currently executing step, which Spring tracks via `StepSynchronizationManager` (a `ThreadLocal`-backed context holder). ## When to use it - Any reader/writer/processor that needs job parameters or the step execution context. - Partitioned steps, where each partition's reader needs partition-specific values from `stepExecutionContext`. - Any bean that must be re-created per step run (e.g. to reset state). ## Gotchas - Forgetting `@StepScope` on a bean that uses `#{jobParameters[...]}` → the expression evaluates at startup and fails. - Accessing a step-scoped bean when no step is running (e.g. from a plain `@PostConstruct` or a non-batch thread) throws `"Scope 'step' is not active for the current thread"`. - `stepExecutionContext` is **not** available in `@JobScope` — only `@StepScope`.

  • What happens if you use #{jobParameters['x']} without @StepScope?
    The SpEL is evaluated when the singleton is created at context startup, where no job is running, so there are no JobParameters — you get a resolution error (often a NullPointerException or 'jobParameters' cannot be found) rather than the runtime value.
  • What is the difference between @StepScope and @JobScope?
    @StepScope creates a new instance per StepExecution and can read both jobParameters and stepExecutionContext; @JobScope creates a new instance per JobExecution (lives across all steps of a job) and can read jobParameters and jobExecutionContext but NOT stepExecutionContext.

saying these in an interview costs you the question

  • Thinking @StepScope beans are singletons created once at startup
  • Believing late binding works without @StepScope/@JobScope
  • Confusing @StepScope with @Scope('prototype') (prototype has no proxy and no step lifecycle)

context