3.4 KiB
5. The diagnostic endpoint: proving four threads ran, not just configuring them
← Previous | README | Next: Why CPU-bound, not I/O-bound →
Configuration says how many partitions should run and on how many threads. It does not say what actually happened on a given execution — whether the pool was saturated, whether two partitions landed on the same thread, how long each one actually took relative to the others. This module makes that observable instead of assumed.
PartitionStatsListener
PartitionStatsListener
is a StepExecutionListener attached to the worker step. beforeStep records a start timestamp on
a ThreadLocal (each partition runs on its own thread, so there is no cross-talk); afterStep
computes the duration and inserts one row into PARTITION_STATS: which step (which partition),
which OS thread name, how many rows it read, when it started and finished, how long it took, and
its exit code.
PartitionInsightController
PartitionInsightController
exposes that table over HTTP:
$ curl -s http://localhost:8081/batch/partitions/1 | python3 -m json.tool
[
{
"PARTITION_NAME": "ordersWorkerStep:partition0",
"THREAD_NAME": "order-partition-2",
"READ_COUNT": 5000,
"STARTED_AT": "2026-09-14T09:29:56.892Z",
"FINISHED_AT": "2026-09-14T09:29:57.968Z",
"DURATION_MS": 1075,
"EXIT_CODE": "COMPLETED"
},
...
]
Full transcript, all four partitions: docs/output/05-happy-path-4-partitions.txt.
Two things this makes concrete that configuration alone does not: partition-to-thread assignment
is not 1:1 with partition number (partition0 landed on thread order-partition-2 here, not
order-partition-1 — TaskExecutorPartitionHandler submits tasks in the order the
Partitioner's map iterates, and ThreadPoolTaskExecutor hands them to whichever pooled thread is
free first); and all four really did start within milliseconds of each other, which is the
difference between "partitioned" as a configuration property and "partitioned" as an observed
fact.
This same table is what makes chapter 7 and
chapter 8 possible to write with confidence:
querying PARTITION_STATS (or, for those chapters, the framework's own BATCH_STEP_EXECUTION
table directly) is how this article found that three rejected partitions do not fail, they hang.
Delete this before shipping. An unauthenticated endpoint that dumps job-internal timing data belongs behind whatever authentication and authorization the rest of the service already has, at minimum, and arguably behind nothing publicly reachable at all — it exists here to make the mechanism visible for this article, not because a production batch service should expose it.
Going deeper
StepExecutionListenerand the other listener interfaces available: Spring Batch reference — Listeners (rel="nofollow").