@Scheduled Observability, Error Handling, and Resilience Patterns
Fast recall
Quick revision
Core answer
@Scheduled tasks need three layers of safety. Observability — log start/end/duration/outcome with MDC, emit Micrometer metrics (@Timed), and alert on missed fires (no run in 2x period). Error handling — wrap the body in try/catch so a single failure doesn't lose context; configure a custom ErrorHandler on the TaskScheduler for uncaught exceptions; record failure metrics and structured logs. Resilience — keep tasks short, delegate heavy work to a queue or Executor, set setWaitForTasksToCompleteOnShutdown(true) for graceful K8s shutdown, and protect against runaway threads via lockAtMostFor (ShedLock) or per-task timeouts.Whether you can operationalize scheduled tasks beyond just declaring `@Scheduled`
Relying on default `ErrorHandler` that just logs, with no alert on consecutive failures or missed fires
Discuss missed-fire detection via heartbeat metric, runaway-thread protection, and the self-invocation pitfall when combining `@Async` with `@Scheduled`
Interview-ready guide
Interview answer
Production @Scheduled tasks need three layers of safety beyond the bare annotation.
Observability:
- Structured logging with MDC —
taskName,runId,triggeredBypropagate through the stack - Micrometer metrics —
scheduled.task.durationandscheduled.task.runswithoutcometag - Heartbeat metric — emit on every successful run; Prometheus alert on staleness (no success in 2x expected period)
- Distributed tracing via
@Observedto correlate with downstream calls
Error handling:
- Wrap the method body in try/catch — preserve MDC, emit failure metric, log with context
- Custom
ErrorHandleron theTaskSchedulerfor uncaught exceptions — default just logs - Distinguish transient from permanent failures — different alert thresholds
- Never silently swallow — even 'expected' exceptions deserve a metric
Resilience:
- Delegate heavy work to a dedicated
Executor— scheduled method runs in milliseconds, finds work, dispatches - Graceful K8s shutdown —
setWaitForTasksToCompleteOnShutdown(true)plusterminationGracePeriodSeconds - Runaway-thread protection — ShedLock
lockAtMostFor, per-task timeouts, or circuit breakers
The @Async + @Scheduled pitfall:
@Asyncworks via AOP proxy; callingthis.asyncMethod()bypasses it- A
@Scheduledmethod callingthis.heavyWorkAsync()does NOT run async - Split across two beans —
TriggerSchedulerwith@ScheduledcallingHeavyWorker.runAsync()
Common production failures these patterns prevent:
- Task throws silently for a week — no one notices because default error handler just logs
- Slow task blocks the scheduler thread, delaying all other scheduled methods
- Lock not released after holder crash — task halted until manual intervention
- K8s rolling deploy kills mid-task without graceful shutdown
- Cron fires at unexpected time after DST transition
Testing tips:
@EnableSchedulingis disabled in test slices — test method directly- Verify cron:
CronExpression.parse(...).next(Instant.now()) - Integration tests simulate time advancement
In an interview I'd emphasize the heartbeat metric — that's the single best safety net for catching silent failures.
@Scheduled Observability, Error Handling, and Resilience Patterns
Direct Answer
Production @Scheduled tasks need three layers of safety. Observability — log start/end/duration/outcome with MDC, emit Micrometer metrics (@Timed), and alert on missed fires (no run in 2x period). Error handling — wrap the body in try/catch so a single failure doesn't lose context; configure a custom ErrorHandler on the TaskScheduler for uncaught exceptions; record failure metrics and structured logs. Resilience — keep tasks short, delegate heavy work to a queue or Executor, set setWaitForTasksToCompleteOnShutdown(true) for graceful K8s shutdown, and protect against runaway threads via lockAtMostFor (ShedLock) or per-task timeouts.
Common Mistakes & Pitfalls
Relying on default ErrorHandler that just logs, with no alert on consecutive failures or missed fires
Check your understanding
Answer these without looking back at the article.
- How do you build a Prometheus alert that fires when a scheduled task hasn't run in 2x its expected period?
- Why does `@Async` on the same bean as `@Scheduled` not work, and how do you fix it?
- How would you protect against a runaway scheduled task that hangs and holds the scheduler thread forever?
- What's the right K8s `terminationGracePeriodSeconds` for a service running scheduled tasks?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.