BuildNot yet confirmed elsewhere1 publisher2 min readPublished
Spring's @Scheduled swallows the exception and reports success to the scheduler
A nightly job that catches its own failure and returns cleanly is invisible to every dashboard you can bolt on afterwards, which is why the durable execution argument lands on the event log rather than the feature list.
The Engineer · Build desk

What happened
- A dev.to guide argues that Spring's @Scheduled breaks down at scale, then walks through migrating a production Spring Batch pipeline to Temporal with complete code examples.
- The failure mode it names is a job that catches its exception, logs nothing meaningful, and returns success to the scheduler that launched it.
- A second mode is overlap: the next cron trigger fires before the previous execution has finished, on jobs where nobody configured @ScheduledLock.
- According to the guide, none of this appears in dashboards until a data audit flags inconsistencies in a critical report.
- Temporal's pitch in the piece is that state transitions go to an event log, so a crashed process replays that log and resumes exactly where it stopped.
Why it matters
- constraint Alerting built on scheduler outcomes cannot tell a swallowed exception from a clean run, so the detection gap survives every monitoring layer added above it.
- exposure Teams that fix overlap with a lock library take on a new dependency whose own corruption nobody is watching, and it is the dependency holding back concurrent writes.
- cost Adoption is paid in rewriting: the fault-tolerant chunk step becomes a workflow method calling separate read, process and write activities, not a configuration change.
Trace the call in the guide's own sample. `jobLauncher.run` throws, the catch block logs the exception, and the method returns normally [2]. The comment sitting in that catch block says what follows: no alert, no retry, no escalation [2]. To the scheduler, the invocation completed, which is the "returns success" the guide puts at the centre of the problem [1]. Anything layered above consumes the same signal. Teams bolt on Actuator, custom logging and Micrometer, and the guide's own verdict is that by the time there is real observability it is a custom framework to maintain [8]. A framework you did not plan to write is still a framework you keep patched.
The interval is what costs money. The sample runs on cron `0 0 2 * * *`, once a night, chunk size 500 [11]. The guide says data corruption from concurrent runs can take weeks to surface [17]. Take two weeks as the low end of that: one run a night for fourteen nights is fourteen executions writing against corrupted state before anyone reads the evidence [20].
Durable execution changes where the history lives. Temporal records every state transition to an event log, and a crashed process is recovered by replaying that log and resuming from the point it stopped [5][6]. The observability is a byproduct of the recovery mechanism: a log detailed enough to reconstruct an execution is detailed enough to answer which step ran and how it ended [15]. Compare that with `BATCH_JOB_INSTANCE` and `BATCH_JOB_EXECUTION`, which the guide notes you manage, prune and keep consistent yourself [10].
The requirement the guide leaves implicit sits in your code. If resuming means replaying recorded state transitions through the same plain Java methods, those methods have to reach the same points in the same order when they are re-executed [16]. That constraint decides whether a given job is a straightforward port or a redesign.
The piece carries no measured numbers, only a before/after diagram and code [13], so nothing here transfers as a benchmark; what transfers is the failure taxonomy, and only if your jobs have the same shape. The guide is straight about that: `@Scheduled` is fine for one or two simple jobs, and the debt accumulates by the time a team has dozens [19]. In my context, a dozen-plus jobs whose only witness is a log line, a queryable execution history is worth the port. At three jobs, the scheduler you already run plus a lock library is the cheaper answer, and the guide concedes that the lock approach works [9].
What to watch
- Whether anyone publishes measured numbers for a migrated pipeline, since the guide argues from diagrams and code rather than metrics.
- Whether Spring's scheduling stack ever ships a default execution history, which would take most of the observability argument off the table.
- Whether migrating teams keep Spring Batch chunking inside activities or rewrite reader, processor and writer outright.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence38
- Adoption
- Insufficient
- Hype gap+30
- Incentives50
- Confidence55
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Scheduled jobs fail silently by catching exceptions, logging nothing meaningful, and returning success to the scheduler.
ReportedSupportedSource: dev.to guide on Temporal vs Spring Batch2 sources— create a free account to open themView cited source - [2]
In the sample scheduler, jobLauncher.run is wrapped in a try/catch whose catch block logs "Invoice reconciliation failed" and carries the comment "No alert. No retry. No escalation."
ReportedSupportedSource: dev.to guide code sample2 sources— create a free account to open themView cited source - [3]
In the guide's after-diagram, a Temporal scheduler triggers a workflow method that calls activities to read, process and write each chunk, with failures auto-retried, crashes replayed from the log and overlap prevented by a workflow ID lock.
ReportedSupportedSource: dev.to guide architecture diagram2 sources— create a free account to open themView cited source - [4]
Jobs run simultaneously because the previous execution had not finished before the next trigger fired and nobody configured @ScheduledLock.
- [5]
Temporal is a durable execution platform: you write plain Java code (functions, loops, try/catch) and Temporal makes it fault-tolerant by recording every state transition to an event log.
- [6]
If the process crashes mid-execution, Temporal replays the event log and resumes from exactly where it left off.
- [7]
With Temporal there is no external state machine to define, no checkpoint tables to maintain and no retry logic to write; the code is the workflow.
- [8]
Spring's scheduler offers no default visibility into which jobs ran, which failed and how long they took; teams bolt on Actuator, custom logging and Micrometer, and by the time there is real observability it is a custom framework to maintain.
- [9]
@ScheduledLock with ShedLock or Quartz clustering works, but it is an additional library, additional config and an additional failure mode, such as the lock row being corrupted.
- [10]
Spring Batch job metadata tables such as BATCH_JOB_INSTANCE and BATCH_JOB_EXECUTION need to be managed, pruned and kept consistent, and the restart/skip/retry model is verbose to configure.
- [11]
The representative example is daily invoice reconciliation on cron "0 0 2 * * *", reading from a pending_invoices table and writing to processed_invoices, with a Spring Batch step of chunk size 500 and @SchedulerLock lockAtMostFor = "PT2H".
- [12]
The dev.to guide explains why @Scheduled breaks down at scale, how Temporal's durable execution model eliminates these problems, and how to migrate a production Spring Batch pipeline with complete code examples.
- [13]
The guide's argument is carried by a before/after architecture diagram and code examples; it presents no measured performance or reliability figures.
- [14]
Monitoring keyed on scheduler invocation outcomes cannot distinguish a swallowed exception from a genuinely successful run.
- [15]
Because recovery works by replaying the recorded log, that log is necessarily detailed enough to answer which step ran and how it ended, so execution visibility is a byproduct of the recovery mechanism rather than a separate feature.
- [16]
Replay-based resumption requires that the workflow code reach the same points in the same order when it is re-executed against the recorded log.
- [17]
Data corruption from concurrent runs can take weeks to surface.
ReportedInsufficientSource: dev.to guide2 sources— create a free account to open themView cited source - [18]
None of this shows up in dashboards until a data audit flags inconsistencies in a critical report.
ReportedInsufficientSource: dev.to guide2 sources— create a free account to open themView cited source - [19]
@Scheduled works fine for one or two simple jobs, but by the time a team has dozens the accumulated debt becomes significant.
ReportedInsufficientSource: dev.to guide2 sources— create a free account to open themView cited source - [20]
Two weeks of a once-nightly job is fourteen executions running against corrupted state before a data audit surfaces the problem.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toTemporal vs Spring Batch: Fixing Enterprise Java's Silent Job Failure Problem
1 article · August 27, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- Scheduled job reliabilityFollow
- Observability of background workFollow
- Durable ExecutionFollow
- Enterprise Java batch processingFollow