skip to content

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%

answer

  1. Step = per StepExecution; Job = per JobExecution
  2. stepExecutionContext is step-only
  3. Both read jobParameters + jobExecutionContext
  4. Partition step = own StepExecution = own instance
  5. Cross-step data via ExecutionContextPromotionListener

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.

solid answer

~40 s

@StepScope gives one instance per StepExecution; @JobScope one per JobExecution (spanning all steps). Choose @JobScope for beans that should be shared and stay alive across the job's steps yet still need late-bound job parameters — for example a shared component configured once from #{jobParameters['x']} and reused by several steps, or a bean written to at job level. Choose @StepScope for readers/writers/processors that are step-local, need re-creation per step, or must read partition-specific data from #{stepExecutionContext['key']} — which @JobScope cannot access because a job has no single step context. Both scopes expose #{jobParameters[...]} and #{jobExecutionContext[...]}. In practice most batch readers/writers are @StepScope; @JobScope is the specialized choice for job-lifetime state and is essential when a decider or a bean must be evaluated once per job rather than per step.

code

java · 22 lines
java
// Job-lifetime shared component, late-bound once per job run.
@Bean
@JobScope
public ReportContext reportContext(
        @Value("#{jobParameters['reportId']}") String reportId,
        @Value("#{jobExecutionContext['tenant']}") String tenant) {
    // ONE instance for the whole job; reused by every step.
    // NOTE: cannot read stepExecutionContext here.
    return new ReportContext(reportId, tenant);
}

// Step-local reader, new instance per step / per partition.
@Bean
@StepScope
public FlatFileItemReader<Row> reader(
        @Value("#{stepExecutionContext['fileName']}") String fileName) {
    return new FlatFileItemReaderBuilder<Row>()
        .name("rowReader")
        .resource(new FileSystemResource(fileName))
        .lineMapper(rowLineMapper())
        .build();
}

go deeper

for a junior

Know @JobScope = per job, @StepScope = per step, and stepExecutionContext is step-only.

for a middle

Map which roots each scope can read and pick the right scope for readers vs shared components.

for a senior

Explain partition-per-StepExecution semantics and cross-step promotion via ExecutionContextPromotionListener.

for a principal

Weigh instance-sharing/lifetime trade-offs and the separate synchronization managers backing each scope.

## Lifecycle difference - **`@StepScope`** → one instance **per `StepExecution`**. If a job has 3 steps, a `@StepScope` bean referenced by all three yields (potentially) 3 distinct instances. In a **partitioned** step, each partition is its own `StepExecution`, so each gets its own instance — that's what makes per-partition late binding work. - **`@JobScope`** → one instance **per `JobExecution`**, living for the whole job across all its steps. ## Late-binding roots available | Root | @StepScope | @JobScope | |------|-----------|-----------| | `#{jobParameters['k']}` | ✅ | ✅ | | `#{jobExecutionContext['k']}` | ✅ | ✅ | | `#{stepExecutionContext['k']}` | ✅ | ❌ (no single step context) | | `#{stepExecution}` | ✅ | ❌ | | `#{jobExecution}` | ✅ | ✅ | The key asymmetry: **`stepExecutionContext` is step-only**. A `@JobScope` bean is not bound to any one step, so there is no step context to read. ## When to choose @JobScope - A bean that should be **created once per job** and **reused across steps** (e.g. an expensive client or a shared accumulator) but still needs late binding of `jobParameters`. - Beans whose value derives from the **whole job**, e.g. a decider input or a listener-fed component reading `jobExecutionContext`. - Avoiding re-instantiation per step when the same configuration applies job-wide. ## When to choose @StepScope - **Readers/writers/processors** — the overwhelmingly common case. - Anything needing **`stepExecutionContext`**: partitioners write partition-specific keys (ranges, file names) into each partition step's `ExecutionContext`, and the step-scoped reader late-binds them. - Beans that must **reset state per step run** (cursors, buffers). ## Passing data across steps Because `stepExecutionContext` is not shared, to move data step→step you promote it to `jobExecutionContext`: ```java @Bean public ExecutionContextPromotionListener promotionListener() { var l = new ExecutionContextPromotionListener(); l.setKeys(new String[]{"computedDate"}); return l; // copies stepExecutionContext['computedDate'] -> jobExecutionContext } ``` Then a later step's `@StepScope` bean reads `#{jobExecutionContext['computedDate']}`. ## Gotchas - Trying `#{stepExecutionContext[...]}` on a `@JobScope` bean → resolution failure / null. - Expecting a single `@StepScope` instance across steps — you get a new one per step; use `@JobScope` if you truly need shared job-lifetime state. - Both scopes still rely on the **same proxy machinery**; `@JobScope` uses `JobSynchronizationManager` instead of `StepSynchronizationManager`. - A `@JobScope` bean accessed outside a running job throws the analogous "Scope 'job' is not active" error. ## Rule of thumb Default to `@StepScope` for I/O components. Reach for `@JobScope` only when you specifically need one instance for the entire job with late-bound parameters.

  • In a partitioned step, why is each partition's reader a separate @StepScope instance?
    Each partition runs as its own StepExecution with its own ExecutionContext, into which the Partitioner writes partition-specific keys. @StepScope gives each partition StepExecution a distinct reader instance that late-binds #{stepExecutionContext['...']} to its own partition's values.
  • How do you make a value computed in one step readable by a bean in a later step?
    Store it in the stepExecutionContext and register an ExecutionContextPromotionListener with that key on the first step, which promotes it to the jobExecutionContext; the later step's @StepScope/@JobScope bean then reads #{jobExecutionContext['key']}.

saying these in an interview costs you the question

  • Claiming @JobScope beans can read stepExecutionContext
  • Assuming one @StepScope instance is shared across all steps of a job
  • Thinking jobParameters is unavailable in @JobScope

context