skip to content

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%

answer

  1. @Value("#{jobParameters['x']}")
  2. Roots: jobParameters, stepExecutionContext, jobExecutionContext
  3. stepExecutionContext only under @StepScope
  4. Elvis ?: for defaults, null on missing key
  5. Typed parameters -> may need conversion

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.

solid answer

~40 s

Late binding means a bean's inputs are resolved at step/job execution time rather than at context-refresh time. You annotate the bean with @StepScope (or @JobScope) and use a SpEL expression in @Value, most commonly @Value("#{jobParameters['inputFile']}"). Because the bean is created per step/job run, Spring Batch exposes late-binding roots to the expression: jobParameters (the launch parameters), stepExecutionContext and jobExecutionContext (the mutable ExecutionContext maps), and also stepExecution/jobExecution. jobParameters values are typed (String/Long/Double/Date), so you may need conversion. Missing keys resolve to null unless you guard with SpEL like #{jobParameters['x'] ?: 'default'}. The whole mechanism depends on the scope annotation: the SpEL evaluates only when the scoped bean is instantiated, which happens once the JobExecution/StepExecution — and therefore the parameters and contexts — exist.

code

java · 14 lines
java
@Bean
@StepScope
public StaxEventItemReader<Trade> reader(
        @Value("#{jobParameters['inputFile']}") String file,
        @Value("#{stepExecutionContext['partition']}") String partition) {
    // 'file' comes from launch parameters (late binding),
    // 'partition' from this step's ExecutionContext (e.g. set by a Partitioner)
    return new StaxEventItemReaderBuilder<Trade>()
        .name("tradeReader-" + partition)
        .resource(new FileSystemResource(file))
        .addFragmentRootElements("trade")
        .unmarshaller(tradeMarshaller())
        .build();
}

go deeper

for a junior

Recognize the @Value("#{jobParameters['x']}") syntax and that it needs @StepScope.

for a middle

List the SpEL roots and handle defaults/nulls and typed parameters correctly.

for a senior

Explain cross-step data passing via jobExecutionContext and ExecutionContextPromotionListener, plus partitioner-written stepExecutionContext keys.

for a principal

Reason about type conversion pitfalls, evaluation timing, and how the synchronization managers expose the roots.

## Late binding defined **Late binding** is Spring Batch's term for injecting values that are only known at **runtime** (when a job/step actually executes) into bean definitions declared at **configuration time**. Normal singleton beans are wired at context startup; late binding pushes that wiring to step/job start so it can see launch-time data. ## The mechanism 1. Annotate the bean with `@StepScope` or `@JobScope` so it is created per run. 2. Use a **SpEL** (`#{...}`) expression inside `@Value` (or the XML/builder equivalent). 3. Spring Batch registers the current execution context via `StepSynchronizationManager` / `JobSynchronizationManager`, exposing special **root objects** to the expression evaluator. ## Available SpEL roots - `#{jobParameters['key']}` — the immutable `JobParameters` supplied at launch. Values are typed (`STRING`, `LONG`, `DOUBLE`, `DATE`). - `#{stepExecutionContext['key']}` — the step's `ExecutionContext` (per-step mutable map). **Only** available under `@StepScope`. - `#{jobExecutionContext['key']}` — the job's `ExecutionContext`, shared across steps. - `#{stepExecution}` and `#{jobExecution}` — the execution objects themselves (e.g. `#{stepExecution.jobExecution.startTime}`). ## Examples ```java @Bean @StepScope public JdbcCursorItemReader<Sale> reader( @Value("#{jobParameters['runDate']}") LocalDate runDate) { return new JdbcCursorItemReaderBuilder<Sale>() .name("salesReader") .dataSource(dataSource) .sql("SELECT * FROM sales WHERE sale_date = ?") .preparedStatementSetter(ps -> ps.setObject(1, runDate)) .rowMapper(new SaleRowMapper()) .build(); } ``` Defaulting and Elvis operator: ```java @Value("#{jobParameters['chunk'] ?: 100}") int chunk ``` ## Gotchas & edge cases - **Type mismatch:** `jobParameters` are stored with an explicit type. If you pass a date as a String parameter but inject into a `LocalDate`, you need a converter or should inject a `String` and parse it. - **Missing key → null:** an absent parameter yields `null`; primitives will NPE on unboxing. Use the Elvis operator `?:` or `Optional`-style handling. - **`stepExecutionContext` under `@JobScope`:** not available — the job scope has no single step context; use `@StepScope` when you need step-level data (common in partitioning, where the partitioner writes keys into each partition's `stepExecutionContext`). - **Passing data between steps:** a common pattern is to write a value into `jobExecutionContext` (or promote a `stepExecutionContext` key via `ExecutionContextPromotionListener`) in one step, then late-bind `#{jobExecutionContext['key']}` in a later step. - **No scope annotation:** the expression evaluates at startup → failure. The scope annotation is mandatory for late binding. ## When to use Any time a reader/writer/processor's configuration depends on launch parameters or on values computed by an earlier step/partitioner.

  • How would you provide a default value if a job parameter is missing?
    Use the SpEL Elvis operator: @Value("#{jobParameters['chunk'] ?: 100}"). If the key is absent (null), it falls back to 100. Otherwise a missing key resolves to null and can NPE on primitive unboxing.
  • How do you pass a value computed in step 1 to a reader in step 2?
    Write it into the jobExecutionContext (or promote a stepExecutionContext key via ExecutionContextPromotionListener), then late-bind it in step 2 with @Value("#{jobExecutionContext['key']}") on a @StepScope bean.

saying these in an interview costs you the question

  • Claiming stepExecutionContext is readable in @JobScope beans
  • Assuming a missing job parameter throws instead of resolving to null
  • Thinking any @Value SpEL is 'late bound' even without a scope annotation

context