Explain JobStep: how do you nest a child Job inside a parent job, and how are the child's JobParameters supplied?
answer
- StepBuilder.job(childJob).launcher(...).parametersExtractor(...)
- Child = separate JobInstance/JobExecution
- DefaultJobParametersExtractor.setKeys(...) from parent context
- Params not auto-passed — extractor bridges them
- Duplicate identifying params -> JobInstanceAlreadyCompleteException
basics
~20 sJobStep is a Step that launches a whole child Job. You build it with StepBuilder.job(childJob), give it a JobLauncher, and a JobParametersExtractor that pulls the child's JobParameters from the parent step/job execution context. The child runs as its own separate JobExecution.
solid answer
~40 s`JobStep` (class `org.springframework.batch.core.step.job.JobStep`) lets a parent job run a **child `Job`** as one of its steps, enabling modular composition of whole jobs. You build it with `new StepBuilder("name", jobRepository).job(childJob).launcher(jobLauncher).parametersExtractor(extractor).build()`. At runtime the JobStep uses the supplied `JobLauncher` to start the child job, so the child gets its **own `JobInstance` and `JobExecution`**, separate from the parent. The child's `JobParameters` are produced by a **`JobParametersExtractor`** — typically `DefaultJobParametersExtractor`, which copies named keys from the parent `StepExecution`'s execution context (or the parent job execution context) into the child's parameters. The parent's JobStep `StepExecution` status is derived from the child `JobExecution` outcome. Because the child has independent identity, its parameters must be unique per run or you hit `JobInstanceAlreadyCompleteException` on re-runs; it also has its own restart boundary and metadata rows.
code
java · 33 linesimport org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.job.DefaultJobParametersExtractor;
import org.springframework.batch.core.step.job.JobParametersExtractor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JobStepConfig {
@Bean
public JobParametersExtractor jobParametersExtractor() {
DefaultJobParametersExtractor extractor = new DefaultJobParametersExtractor();
// keys are read from the parent StepExecution's execution context
extractor.setKeys(new String[] { "inputFile", "runId" });
return extractor;
}
@Bean
public Step childJobStep(JobRepository jobRepository,
Job childJob,
JobLauncher jobLauncher,
JobParametersExtractor jobParametersExtractor) {
return new StepBuilder("childJobStep", jobRepository)
.job(childJob) // nest a whole Job
.launcher(jobLauncher) // launch it
.parametersExtractor(jobParametersExtractor) // supply child params
.build();
}
}go deeper
Can say JobStep runs a child Job as a step; details of parameters/identity likely beyond scope.
Knows StepBuilder.job(child) plus a JobLauncher and that the child runs separately, but may be shaky on the extractor.
Explains the JobParametersExtractor mechanism, separate JobInstance identity, status propagation, and the duplicate-parameter restart pitfall.
Weighs JobStep vs FlowStep vs a message-driven/orchestrator approach, and reasons about restart boundaries spanning parent and child instances in production.
## The goal: compose whole jobs Sometimes you want to reuse an entire, independently-runnable `Job` as a building block of a bigger job. **`JobStep`** wraps a child `Job` so it appears as a `Step` in the parent. This is heavier than `FlowStep`: it is a true nested job, not just reused step-wiring. ## Building a JobStep `StepBuilder.job(Job)` returns a `JobStepBuilder`: ```java @Bean Step runChildAsStep(JobRepository jobRepository, Job childJob, JobLauncher jobLauncher) { return new StepBuilder("childJobStep", jobRepository) .job(childJob) .launcher(jobLauncher) // how to start the child .parametersExtractor(jobParametersExtractor()) .build(); } ``` Required collaborators: - **`.job(childJob)`** — the nested `Job`. - **`.launcher(JobLauncher)`** — used to actually launch the child. (If omitted, the JobStep expects a launcher to be available; be explicit.) - **`.parametersExtractor(JobParametersExtractor)`** — computes the child's `JobParameters`. ## How the child gets its JobParameters This is the most-tested detail. The parent's `JobParameters` are **not** automatically passed down. A **`JobParametersExtractor`** (`org.springframework.batch.core.step.job.JobParametersExtractor`) is invoked with the parent `StepExecution` and returns the child `JobParameters`. The stock implementation is **`DefaultJobParametersExtractor`**. You configure it with a set of key names; it looks those keys up in the parent **StepExecution's ExecutionContext** (and can be pointed at the job execution context via `setUseAllParentParameters`/key configuration) and maps them into child parameters: ```java @Bean JobParametersExtractor jobParametersExtractor() { DefaultJobParametersExtractor extractor = new DefaultJobParametersExtractor(); extractor.setKeys(new String[] { "inputFile", "runId" }); return extractor; } ``` A common pattern is to have an earlier step (or the parent job) put values into the execution context under those keys so the extractor can forward them. ## Execution identity and status propagation - The child runs via `JobLauncher`, producing a **separate `JobInstance`/`JobExecution`** with its own step-execution rows. - The parent JobStep's `StepExecution` status/exit status is **derived from the child `JobExecution`** — child `COMPLETED` → parent step completes; child `FAILED` → parent step fails and (by default) fails the parent job. ## Gotchas (the reason this is a senior question) - **Parameter uniqueness / identity**: because the child is a real job instance, re-launching it with **identical identifying parameters** throws `JobInstanceAlreadyCompleteException` (a completed instance cannot re-run) or `JobRestartException`. Include a unique identifying parameter (e.g. a `runId`/timestamp) via the extractor when the child should run every time. - **Restart semantics**: the child job has its **own restart boundary**. Restarting the parent restarts the JobStep, which resumes/re-launches the child according to the child's own metadata — reasoning about "which execution restarts" spans two instances. - **Transaction boundary**: the child runs in its own executions/transactions; the parent step does not wrap it in a single transaction. - **Not the same as a split**: JobStep is about nesting a *job*, not parallelizing steps (parallel split is a separate concern). - **JobLauncher choice**: using a synchronous launcher runs the child inline; be careful mixing with async launchers inside a step. ## When to use JobStep vs FlowStep Reach for **JobStep** when the child must be an **independently runnable, independently parameterized, independently restartable** job (e.g. an existing standalone job you also orchestrate from a parent). Reach for **FlowStep** when you only want to reuse a **sequence of steps** as a unit within the same job execution — lighter, no separate identity or parameters.
- The parent job passes an inputFile parameter. Will the child job automatically receive it?No. Parent JobParameters are not auto-forwarded. A JobParametersExtractor (e.g. DefaultJobParametersExtractor with the key 'inputFile') must copy it from the parent step/job execution context into the child's JobParameters.
- Why might a nested child job throw JobInstanceAlreadyCompleteException on the second parent run?The child is a real JobInstance identified by its identifying parameters. If the extractor produces the same identifying parameters, Spring Batch sees an already-completed instance and refuses to re-run it. Add a unique identifying parameter (e.g. runId/timestamp).
- How does the child job's failure affect the parent?The JobStep's StepExecution derives its status from the child JobExecution. A FAILED child fails the JobStep, which by default fails the parent job unless a transition handles that exit status.
saying these in an interview costs you the question
- Assuming parent JobParameters are automatically inherited by the child
- Thinking the child runs under the parent's JobExecution (it gets its own)
- Not knowing DefaultJobParametersExtractor / how the extractor works
- Ignoring the identity/restart pitfall (JobInstanceAlreadyCompleteException)