skip to content

@StepScope / @JobScope & Late Binding

@StepScope and @JobScope beans are created per run behind proxies, which is what allows job parameters to be bound late into a reader or writer. Interviewers ask how a file name from the parameters reaches the reader, and this is the mechanism.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

5

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

open as a page

How does late binding of job parameters into a Spring Batch bean work, and what SpEL expressions can you use?

level: middleimportance: must knowfreq 60%

basics

~10 s

You inject runtime values with SpEL like @Value("#{jobParameters['x']}") on a bean annotated @StepScope or @JobScope. Spring resolves the expression when the step/job runs instead of at startup. Available roots include jobParameters, stepExecutionContext, and jobExecutionContext.

open as a page

When would you choose @JobScope over @StepScope, and how do the late-binding roots (jobParameters vs stepExecutionContext vs jobExecutionContext) differ between them?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Use @JobScope when a bean must live for the whole job (across steps); use @StepScope when it's per step or needs the step's ExecutionContext. Both can read jobParameters and jobExecutionContext, but only @StepScope can read stepExecutionContext.

open as a page

How does @StepScope actually work under the hood — why can a singleton Step inject a step-scoped reader, and what role does the scoped proxy play?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Spring injects a scoped proxy, not the real bean. The singleton Step holds this proxy; on each method call the proxy looks up the real step-scoped instance bound to the currently running step and delegates to it. That deferral is what makes late binding possible.

open as a page

A developer sees 'Scope "step" is not active for the current thread' and, separately, notices a @StepScope reader captured a stale job parameter across launches. Diagnose both and explain the underlying threading/lifecycle model.

level: principalimportance: should knowfreq 24%

basics

~20 s

The 'scope not active' error means the step-scoped bean was touched when no StepExecution was bound to the thread — e.g. in @PostConstruct, from another thread, or before launching. Stale parameters usually mean the bean isn't actually @StepScope (a singleton captured the value once), or state leaked because the instance was shared.

open as a page