Where do StepExecution and the ExecutionContext live relative to JobInstance and JobExecution, and why does it matter?
answer
- Instance = pure identity, no runtime state
- JobExecution owns status + StepExecutions + context
- StepExecution owns read/write/skip/commit counts
- Counts are per-attempt, not summed on instance
- ExecutionContext is the cross-attempt bridge
basics
~20 sStepExecutions and the ExecutionContext belong to a JobExecution, not directly to the JobInstance. Each run attempt has its own StepExecutions and context. Because there can be many JobExecutions per instance, each attempt gets a fresh set.
solid answer
~40 sA JobExecution owns one StepExecution per step it runs, plus a job-level ExecutionContext; each StepExecution has its own step-level ExecutionContext. None of these hang directly off the JobInstance — the instance is just the logical identity. This matters because a JobInstance can have several JobExecutions (on restart): each attempt gets its own StepExecutions with their own read/write/skip/commit counts and its own context. On restart, Spring Batch copies forward the persisted ExecutionContext so restartable steps resume, but the new JobExecution still records a distinct set of StepExecutions. So per-attempt metrics are isolated, while the ExecutionContext is the state that bridges attempts. Confusing this leads people to expect cumulative counts on the instance, when in fact counts are per JobExecution/StepExecution.
code
java · 17 linesJobExecution exec = jobLauncher.run(job, params);
// Runtime state lives on the execution, not the instance:
for (StepExecution step : exec.getStepExecutions()) {
System.out.println(step.getStepName()
+ " read=" + step.getReadCount() // per-attempt counter
+ " write=" + step.getWriteCount()
+ " skip=" + step.getWriteSkipCount());
}
// The JobInstance itself carries only identity:
JobInstance instance = exec.getJobInstance();
instance.getJobName(); // present
// instance has no getReadCount(), no BatchStatus, no ExecutionContext.
// The ExecutionContext (what survives restart) is on the execution:
ExecutionContext ctx = exec.getExecutionContext();go deeper
Should at least know StepExecution belongs under a run attempt.
Expected to place StepExecution/ExecutionContext under JobExecution and know counts are per-attempt.
Should explain the ExecutionContext as the restart bridge and the two context scopes.
Should reason about context serialization limits, listener firing per attempt, and computing instance-level aggregates.
## The ownership hierarchy Spring Batch's domain objects form a strict containment tree, and knowing which object owns what avoids a class of bugs and misreadings. ``` JobInstance (logical run; identity = name + identifying params) └── JobExecution (one attempt; status, timings, ExitStatus) ├── ExecutionContext (job-level state) └── StepExecution (one per step, per attempt) └── ExecutionContext (step-level state; read/skip position, etc.) ``` ### JobInstance owns nothing runtime The **`JobInstance`** is deliberately thin: a name, an id, and the `JobKey` derived from identifying parameters. It carries **no status, no counts, no context** — it is pure identity. Its runtime children live under executions. ### JobExecution owns the runtime state Each **`JobExecution`** holds: - **`BatchStatus`** and **`ExitStatus`** for the attempt, - `createTime`/`startTime`/`endTime`, - a job-level **`ExecutionContext`** (persisted to `BATCH_JOB_EXECUTION_CONTEXT`), - a collection of **`StepExecution`s**. ### StepExecution owns per-step metrics Each **`StepExecution`** (row in `BATCH_STEP_EXECUTION`) has: - its own `BatchStatus`/`ExitStatus`, - counters: `readCount`, `writeCount`, `commitCount`, `rollbackCount`, `readSkipCount`, `writeSkipCount`, `filterCount`, - its own step-level **`ExecutionContext`** (`BATCH_STEP_EXECUTION_CONTEXT`) — where a restartable reader stores its position. ### Why the placement matters **1. Metrics are per-attempt, not cumulative on the instance.** If a run fails after processing 5,000 items and a restart processes the remaining 3,000, you have **two** `StepExecution`s: one with `writeCount≈5000` (FAILED attempt) and one with `writeCount≈3000` (restart). There is no single instance-level 'total 8000' field — you compute it by summing across executions if you need it. Expecting cumulative counts on the instance is a common misread. **2. ExecutionContext is the bridge across attempts.** The `ExecutionContext` is the *only* runtime state that carries forward: on restart, Spring Batch reloads the persisted context so restartable `ItemStream` components resume. Everything else (a new `StepExecution`, fresh counters) starts clean for the new attempt. **3. Scope for `@StepScope`/`@JobScope` beans.** Late-bound beans are tied to the current `StepExecution`/`JobExecution` lifecycle — again, the execution, not the instance. `jobParameters` and context values are injected from the running execution. ### Gotchas - Reading `readCount`/`writeCount` off 'the job' means picking a specific `JobExecution` (usually the last), not the instance. - The job-level and step-level `ExecutionContext`s are **separate**; putting data in the wrong one affects what is visible where and what survives restart. - `ExecutionContext` contents are serialized into the metadata tables — keep them small and serializable; do not stash large objects. - Because each attempt has its own `StepExecution`, listeners (`StepExecutionListener`) fire per attempt, not once per instance.
- A job failed after writing 5,000 rows and a restart wrote the remaining 3,000. Where do you read the 'total 8,000' figure?There is no single field for it. You sum writeCount across the StepExecutions of both JobExecutions belonging to that JobInstance — the FAILED attempt (~5,000) plus the restart attempt (~3,000). Counts are per-execution, not aggregated on the instance.
- Which piece of state carries forward from a failed attempt to its restart, and where is it stored?The ExecutionContext (job-level and step-level), persisted in BATCH_JOB_EXECUTION_CONTEXT and BATCH_STEP_EXECUTION_CONTEXT. Restartable ItemStream components reload it to resume; StepExecution counters themselves start fresh for the new attempt.
saying these in an interview costs you the question
- Attaching StepExecutions or ExecutionContext to the JobInstance
- Expecting cumulative read/write counts on the instance
- Confusing job-level and step-level ExecutionContext
- Storing large non-serializable objects in the ExecutionContext