Skip to content

Observability

Everything the cluster knows about itself lives in the coordination database, so it can be inspected with ordinary SQL — but you rarely need to. The library ships Spring Boot Actuator integrations that surface the same state as JSON, in two flavours:

  • Node-centric — "what does the cluster look like right now, node by node?"
  • Job-centric — "for this job execution, how are its partitions placed and progressing?"

Plus health indicators and an /actuator/info contributor. All of it is read-only and runs off the coordination tables, off the hot polling path.

You must expose these endpoints

Like all non-health actuator endpoints, these are not exposed over HTTP by default. Add the ones you want to management.endpoints.web.exposure.include:

management:
  endpoints:
    web:
      exposure:
        include: "health,info,batch-cluster,batch-cluster-jobs"
  endpoint:
    health:
      show-details: always   # needed to see the cluster health details below

The endpoint IDs are batch-cluster and batch-cluster-jobs. They only register when clustering is enabled (spring.batch.cluster.enabled=true).


Health — /actuator/health

The library contributes two health components. With show-details: always:

{
  "components": {
    "batchCluster": {
      "status": "UP",
      "details": {
        "Total Nodes in Cluster": "2",
        "Total Active Nodes": "2"
      }
    },
    "batchClusterNode": {
      "status": "UP",
      "details": {
        "Node Id": "node-8bde6cfa-0505-4f0a-a542-2c37a16b1936",
        "Node Status": "ACTIVE",
        "Current Load": "0",
        "Start Time": "Fri Jul 10 11:46:19 EDT 2026",
        "Last Heartbeat Update Time": "Fri Jul 10 11:46:40 EDT 2026"
      }
    }
  }
}
Component Reports UP when DOWN when
batchCluster cluster-wide view: total registered nodes and how many are ACTIVE at least one node is registered the registry is empty (e.g. the database is unreachable)
batchClusterNode this node's own id, status, load, start time, last heartbeat this node's status is ACTIVE this node is UNREACHABLE (its own heartbeat has lapsed)

batchClusterNode going DOWN is a useful readiness signal — a node that has lost its heartbeat self-fences (stops claiming work), and this reflects that before the master removes it.


Info — /actuator/info

Publishes the running library version and the effective cluster settings — handy for confirming what timings a node is actually using (support, debugging, drift checks):

{
  "Batch Clustering Properties": {
    "Batch clustering version": "3.0.0",
    "Node heartbeat interval (milli seconds)": 1000,
    "Check for tasks polling interval (milli seconds)": 1000,
    "Node unreachable marking threshold (milli seconds)": 3000,
    "Node delete threshold (milli seconds)": 5000,
    "Master task status check interval (milli seconds)": 500,
    "Check for orphaned tasks interval (milli seconds)": 1000,
    "Concurrency limit per node": 10,
    "Node unreachable marking-thread interval (milli seconds)": 5000,
    "Node cleanup-thread interval (milli seconds)": 5000
  }
}

The version is read from the jar manifest, so it always matches the artifact on the classpath.


Cluster overview (node-centric)

/actuator/batch-cluster

Every node currently in the registry, with its status, host, heartbeat, and live load:

{
  "totalNodes": 2,
  "nodes": [
    {
      "Node Id": "node-8bde6cfa-0505-4f0a-a542-2c37a16b1936",
      "Host": "Mac.lan",
      "Status": "ACTIVE",
      "Started At": "2026-07-10T15:46:19.954Z",
      "Last Heartbeat": "2026-07-10T15:46:29.963Z",
      "Current Load (# of tasks)": 0
    },
    {
      "Node Id": "node-d4bfd102-f884-4fdc-9b7c-eefa7ff155b1",
      "Host": "Mac.lan",
      "Status": "ACTIVE",
      "Started At": "2026-07-10T15:46:22.625Z",
      "Last Heartbeat": "2026-07-10T15:46:30.639Z",
      "Current Load (# of tasks)": 0
    }
  ]
}

Status is ACTIVE or UNREACHABLE. Current Load (# of tasks) is the number of partitions the node is running right now — the same figure the LEAST_LOADED assignment strategy uses.

/actuator/batch-cluster/{nodeId}

One node's detail (the raw ClusterNodeInfo):

{
  "nodeId": "node-8bde6cfa-0505-4f0a-a542-2c37a16b1936",
  "hostIdentifier": "Mac.lan",
  "nodeStatus": "ACTIVE",
  "currentLoad": 0,
  "startTime": "2026-07-10T15:46:19.954Z",
  "lastHeartbeatTime": "2026-07-10T15:46:30.963Z"
}

Job view (job-centric)

This is the view for answering "where did the partitions of this job go, and how are they doing?"

/actuator/batch-cluster-jobs

The jobs currently coordinated by the cluster, one row each:

[
  {
    "jobExecutionId": 1,
    "masterNode": "node-d3706bd7-0d54-4011-aa53-49ec6395ab53",
    "masterStepName": "multiNodeWorkerStep",
    "coordinationStatus": "STARTED"
  }
]

coordinationStatus is CREATEDSTARTEDCOMPLETED (or ABANDONED if a master was lost and the job was recovered).

/actuator/batch-cluster-jobs/{jobExecutionId}

The full breakdown: the master, a status histogram, and every partition with the node it landed on.

While running — four partitions claimed across two nodes:

{
  "jobExecutionId": 1,
  "masterNode": "node-d3706bd7-0d54-4011-aa53-49ec6395ab53",
  "coordinationStatus": "STARTED",
  "partitionCount": 4,
  "statusCounts": { "CLAIMED": 4 },
  "partitions": [
    { "stepExecutionId": 2, "partitionKey": "multiNodeWorkerStep:0", "assignedNode": "node-d3706bd7-0d54-4011-aa53-49ec6395ab53", "status": "CLAIMED" },
    { "stepExecutionId": 3, "partitionKey": "multiNodeWorkerStep:1", "assignedNode": "node-c96bd358-e2f6-425e-9f04-37c4d210efc9", "status": "CLAIMED" },
    { "stepExecutionId": 4, "partitionKey": "multiNodeWorkerStep:2", "assignedNode": "node-d3706bd7-0d54-4011-aa53-49ec6395ab53", "status": "CLAIMED" },
    { "stepExecutionId": 5, "partitionKey": "multiNodeWorkerStep:3", "assignedNode": "node-c96bd358-e2f6-425e-9f04-37c4d210efc9", "status": "CLAIMED" }
  ]
}

After completion — the histogram flips to all COMPLETED:

{
  "jobExecutionId": 1,
  "masterNode": "node-d3706bd7-0d54-4011-aa53-49ec6395ab53",
  "coordinationStatus": "COMPLETED",
  "partitionCount": 4,
  "statusCounts": { "COMPLETED": 4 },
  "partitions": [ "… four partitions, each status COMPLETED …" ]
}
Field Meaning
masterNode the node that launched (and coordinates) this job execution
partitionCount total partitions the master created for the step
statusCounts histogram over partition status: PENDING, CLAIMED, COMPLETED, FAILED
partitions[].assignedNode the node a partition is assigned to right now
partitions[].status that partition's lifecycle state

Because assignedNode reflects the current owner, this endpoint is also how you watch failover: when a worker is lost, its transferable partitions reappear here assigned to a surviving node (and the statusCounts momentarily shows them back in PENDING/CLAIMED before completing).


Programmatic access — BatchClusterQueryService

The two job endpoints are thin wrappers over a public, read-only service bean. Inject it to build your own API, UI, or metrics on the same stable read-model DTOs — no need to go through actuator:

@Autowired
BatchClusterQueryService queryService;

List<JobSummaryView> jobs = queryService.listCoordinatedJobs();
Optional<JobClusterView> view = queryService.getJobView(jobExecutionId);

JobSummaryView, JobClusterView, and PartitionView are the same records serialized by the endpoints above. The service issues plain read-only SELECT/GROUP BY queries and is scoped to live and recent jobs (full history/retention is out of scope by design — see the roadmap).


Security

Restrict exposure

These endpoints reveal operational detail — node hosts, partition assignments, job coordination state. Treat them like any other actuator endpoint: expose only what you need (management.endpoints.web.exposure.include), and protect them with authentication/authorization. Never expose batch-cluster / batch-cluster-jobs unauthenticated on a public interface. A common pattern is a separate, firewalled management port (management.server.port) reachable only from your ops network. See Security for the full hardening guidance.