Quartz Misfires, Recovery, and Spring Batch JobInstance Uniqueness
Fast recall
Quick revision
Core answer
fireAndProceed for catch-up-once semantics, doNothing for skip-and-wait. `requestRecovery(true)` on a JobDetail causes the trigger to re-fire on another node when its owner crashes mid-execution. Both interact with Spring Batch's JobInstance uniqueness rule — same Job name plus same JobParameters is one logical instance. Crash-safe design means: include a timestamp or run-id parameter so each fire creates a new instance; make the batch job idempotent so recovery doesn't double-process; and ensure the Quartz JobDetail and Spring Batch Job names are decoupled so you can rename triggers without breaking job restart.Whether you understand the interplay between Quartz semantics and Spring Batch's identity model under failure scenarios
Setting `requestRecovery(true)` on a non-idempotent job, or using fixed parameters that cause `JobInstanceAlreadyCompleteException` on the recovery fire
Discuss the difference between Quartz's job-level recovery and Spring Batch's step-level restart, and how they compose without duplicating work
Interview-ready guide
Interview answer
Quartz and Spring Batch each have their own failure semantics, and the two compose subtly.
Quartz's failure axis:
- Misfire — trigger should have fired but didn't (scheduler down, thread pool busy)
- Recovery — a job running on a dead node re-fires on a live node when
requestRecovery(true)is set
Spring Batch's failure axis:
- JobInstance = Job name + JobParameters; uniqueness enforced
- JobExecution = one attempt of a JobInstance; FAILED executions can be restarted
The composition rule:
- Decide explicitly whether each scheduled fire is a new logical run or a continuation
- New run per fire — include timestamp/fireInstanceId parameter; recovery creates a new JobInstance
- Continuation — keep parameters constant per logical period; recovery restarts the FAILED execution
Misfire policies:
fireAndProceed— fire once now, skip intermediate; good for daily reportsdoNothing— skip missed, wait for next; good for high-frequency syncsignoreMisfires— rarely what you want- Never leave the default
SMART_POLICY— vendor-specific behavior
`requestRecovery(true)` semantics:
- Recovered job re-fires with the same parameters as the original
- Original was COMPLETED →
JobInstanceAlreadyCompleteException - Original was STARTED at crash → restart resumes from last committed step
- Original still in STARTED status (no cleanup) → restart fails with
JobExecutionAlreadyRunningException
Operational hygiene:
- Cleanup job marks abandoned
JobExecutions in STARTED status as FAILED after threshold - Metrics on misfire count, recovery count, job duration
- Decouple Quartz JobDetail names from Spring Batch Job names — use
JobRegistry.getJob(name)lookup
Idempotency requirements:
- Recovered jobs may double-process committed chunks if the batch job is non-idempotent
- UPSERT writers, idempotency keys, outbox pattern
- Side effects (emails, API calls) — defer to a final step or use outbox
Anti-patterns:
requestRecovery(true)on non-idempotent jobs- Same parameters every run + restart-on-recovery semantics
- No cleanup of abandoned executions
- Default
SMART_POLICYmisfire
In an interview I'd emphasize the JobInstance uniqueness rule — that's the constraint Quartz semantics have to compose around.
Quartz Misfires, Recovery, and Spring Batch JobInstance Uniqueness
Direct Answer
Quartz misfire policies control what happens when a trigger can't fire on time — fireAndProceed for catch-up-once semantics, doNothing for skip-and-wait. requestRecovery(true) on a JobDetail causes the trigger to re-fire on another node when its owner crashes mid-execution. Both interact with Spring Batch's JobInstance uniqueness rule — same Job name plus same JobParameters is one logical instance. Crash-safe design means: include a timestamp or run-id parameter so each fire creates a new instance; make the batch job idempotent so recovery doesn't double-process; and ensure the Quartz JobDetail and Spring Batch Job names are decoupled so you can rename triggers without breaking job restart.
Common Mistakes & Pitfalls
Setting requestRecovery(true) on a non-idempotent job, or using fixed parameters that cause JobInstanceAlreadyCompleteException on the recovery fire
Check your understanding
Answer these without looking back at the article.
- What happens when Quartz recovers a job whose original JobExecution is still in STARTED status?
- How would you design a daily batch job so that recovery picks up the failed execution rather than creating a new one?
- Why is the default misfire policy SMART_POLICY a poor choice for production batch jobs?
- How does the cleanup job for abandoned executions interact with Quartz's recovery mechanism?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.