skip to content

Where are BatchStatus and ExitStatus persisted, and what practical problems does storing them separately solve?

level: seniorimportance: should knowfreq 22%

answer

  1. STATUS column = BatchStatus enum name
  2. EXIT_CODE + EXIT_MESSAGE = ExitStatus
  3. Restart keys off STATUS, not EXIT_CODE
  4. STATUS=COMPLETED with EXIT_CODE=custom is valid
  5. EXIT_MESSAGE truncates stack traces

basics

~20 s

The JobRepository stores both in the batch metadata tables: a STATUS column holds the BatchStatus name, and an EXIT_CODE/EXIT_MESSAGE column holds the ExitStatus. Keeping them separate lets you record the canonical lifecycle and a flexible outcome code independently.

solid answer

~40 s

Spring Batch's JobRepository persists both statuses on JobExecution and StepExecution rows. In the standard schema, BATCH_JOB_EXECUTION and BATCH_STEP_EXECUTION each have a STATUS column (the BatchStatus enum name as a String) plus EXIT_CODE and EXIT_MESSAGE columns (the ExitStatus code and description). Storing them separately is deliberate: STATUS is the canonical, closed-enum lifecycle the framework relies on for restart and monitoring, while EXIT_CODE is an open String you can enrich for branching or reporting without corrupting the lifecycle record. This is why a row can read STATUS=COMPLETED, EXIT_CODE=COMPLETED_WITH_SKIPS. On restart, the framework inspects STATUS (e.g. FAILED/STOPPED are restartable, COMPLETED usually is not), independent of whatever custom exit code you stored. The separation keeps machine-critical semantics stable while allowing human/flow-facing outcomes to vary.

code

java · 13 lines
java
// Reading persisted metadata via JobExplorer
JobExecution exec = jobExplorer.getJobExecution(executionId);

BatchStatus status = exec.getStatus();          // <- BATCH_JOB_EXECUTION.STATUS
ExitStatus exit    = exec.getExitStatus();      // <- EXIT_CODE + EXIT_MESSAGE

System.out.println(status);                     // e.g. COMPLETED
System.out.println(exit.getExitCode());         // e.g. COMPLETED_WITH_SKIPS
System.out.println(exit.getExitDescription());  // possibly truncated trace

// Restart decision is made by the framework using STATUS, not exit code:
// COMPLETED  -> JobInstanceAlreadyCompleteException on relaunch
// FAILED/STOPPED -> resumes

go deeper

for a junior

Knows both are stored in batch metadata tables.

for a middle

Identifies STATUS vs EXIT_CODE/EXIT_MESSAGE columns and that both persist.

for a senior

Explains restart keys off STATUS not EXIT_CODE and the rationale for separation.

for a principal

Discusses operational overrides (ABANDONED), truncation pitfalls, and observability/alerting design around the two fields.

## The metadata tables Spring Batch stores execution history through the `JobRepository`, backed by the standard schema (`schema-*.sql`). The relevant tables: - `BATCH_JOB_EXECUTION` — one row per job run. - `BATCH_STEP_EXECUTION` — one row per step run. Both tables carry: - a **`STATUS`** column — the `BatchStatus` **enum name** stored as text (e.g. `'COMPLETED'`, `'FAILED'`). - an **`EXIT_CODE`** column — the `ExitStatus.exitCode` String. - an **`EXIT_MESSAGE`** column — the `ExitStatus.exitDescription` (often a truncated stack trace). ## Why two columns instead of one The separation directly reflects the domain design: - **STATUS (BatchStatus)** is the **canonical lifecycle record**. The framework reads it for restart decisions, running-detection, and reporting. It must stay a small, closed, machine-trusted vocabulary. - **EXIT_CODE (ExitStatus)** is the **flexible outcome code**. It can be customized (`COMPLETED_WITH_SKIPS`, `NO_INPUT`, etc.) to drive flow branching or richer reporting. Because they are separate columns, a step can legitimately store `STATUS='COMPLETED'` with `EXIT_CODE='COMPLETED_WITH_SKIPS'` — the lifecycle stays truthful while the outcome is enriched. ## Restart semantics rely on STATUS When you relaunch a job with the same identifying `JobParameters`, the framework looks at the previous **BatchStatus**: - `COMPLETED` — the JobInstance is considered done; by default it cannot be rerun (`JobInstanceAlreadyCompleteException`). - `FAILED` / `STOPPED` — restartable; execution resumes. - `ABANDONED` — skipped on restart. - `UNKNOWN` — unsafe; requires manual resolution. Crucially, this logic keys off STATUS, **not** the custom EXIT_CODE. If restart decisions used the free-form exit code, a custom string could accidentally break restartability — the separation prevents that. ## EXIT_MESSAGE and failures When a step fails, the exception's stack trace is captured into the ExitStatus description and persisted to `EXIT_MESSAGE`. This column is length-limited in the schema, so long traces are truncated — a common surprise when debugging from the DB. ## Observability Dashboards and Spring Batch's own querying (`JobExplorer`, `JobOperator`) expose both fields. Operators typically filter/alert on STATUS (authoritative) but display EXIT_CODE for finer outcome context. ## Gotchas - Don't drive restart or alerting off EXIT_CODE alone; it's customizable and non-canonical. - EXIT_MESSAGE truncation can hide root causes — log the full exception separately. - Manually editing STATUS in the DB (e.g. FAILED -> ABANDONED) is a supported operational action but must be done deliberately; JobOperator exposes safer APIs.

  • Why not just persist one status field?
    Because the two serve different masters: STATUS is a closed, framework-trusted vocabulary for restart/monitoring, while EXIT_CODE is an open, customizable outcome for branching/reporting. Merging them would either lock down flexibility or let arbitrary strings break restart logic.
  • You customized EXIT_CODE but restart still refuses to rerun a COMPLETED job — why?
    Restart keys off BatchStatus (STATUS=COMPLETED), not your custom exit code. To rerun you must supply new identifying JobParameters or use an allow-start-if-complete setting; the exit code has no bearing.

saying these in an interview costs you the question

  • Thinking restartability is decided from the exit code.
  • Assuming EXIT_MESSAGE stores the full untruncated stack trace.
  • Believing there's a single status column in the metadata schema.

context