Monitor Spring Boot scheduled tasks
A @Scheduled method fails quietly. The exception is logged and the next run
tries again, or the scheduler thread is stuck behind a slow task, or the app
instance that runs it was scaled to zero. A
heartbeat check catches all of these: the task
reports to Pingstack after it succeeds, and Pingstack alerts you when that
report doesn’t arrive on time.
1. Create a heartbeat check that matches the schedule
Section titled “1. Create a heartbeat check that matches the schedule”Spring cron expressions have six fields, starting with seconds:
@Scheduled(cron = "0 0 2 * * *", zone = "UTC")Pingstack uses standard five-field cron. Drop the seconds field:
| Spring | Pingstack |
|---|---|
0 0 2 * * * |
0 2 * * * |
0 30 6 * * MON-FRI |
30 6 * * 1-5 |
0 0 */4 * * * |
0 */4 * * * |
fixedRate = 900000 |
Interval of 15 minutes |
Create a heartbeat check with the
converted schedule. Without zone, Spring uses the server’s timezone; set
zone explicitly and choose the same timezone in Pingstack. See
schedules and timezones.
Set a grace period longer than the task’s usual runtime. Attach a notification group.
2. Store the ping URL in configuration
Section titled “2. Store the ping URL in configuration”Copy the location’s ping URL from the check in Pingstack and provide it as an
environment variable, such as PINGSTACK_NIGHTLY_URL, through your platform’s
secret settings. Map it to a property in application.yml:
pingstack: nightly-url: ${PINGSTACK_NIGHTLY_URL:}3. Report after the task succeeds
Section titled “3. Report after the task succeeds”@Componentpublic class NightlyReport {
private static final Logger log = LoggerFactory.getLogger(NightlyReport.class); private final RestClient http = RestClient.create(); private final ReportService reports;
@Value("${pingstack.nightly-url}") private String pingUrl;
public NightlyReport(ReportService reports) { this.reports = reports; }
@Scheduled(cron = "0 0 2 * * *", zone = "UTC") public void run() { reports.sendNightly(); reportToPingstack(); }
private void reportToPingstack() { if (pingUrl == null || pingUrl.isBlank()) return; try { http.get().uri(pingUrl).retrieve().toBodilessEntity(); } catch (RestClientException e) { log.warn("Pingstack heartbeat failed: {}", e.getClass().getSimpleName()); } }}The report runs only after sendNightly() returns, so a run that throws sends
nothing and Pingstack alerts you when the deadline passes. Make sure
scheduling is turned on with @EnableScheduling on a configuration class.
RestClient needs Spring Framework 6.1 or later. On older versions, use
RestTemplate the same way, and set a timeout on whichever client you use.
Several app instances
Section titled “Several app instances”Every instance runs @Scheduled methods, so each one reports, which hides an
instance that stopped. Run the task on one instance with a lock such as
ShedLock, or move it to a dedicated worker.
If you use Quartz instead, report from the end of the job’s execute method
in the same way.
4. Confirm the first report
Section titled “4. Confirm the first report”Call the method once, for example from a test profile or an admin endpoint you already have, with the same configuration. Open the check in Pingstack and confirm the location received a heartbeat and shows a next deadline.
Optional: report a number
Section titled “Optional: report a number”Append a count as a numeric value and set warning and critical thresholds on the check:
int sent = reports.sendNightly();http.get().uri(pingUrl + "?value=" + sent).retrieve().toBodilessEntity();Why a healthy task might still alert
Section titled “Why a healthy task might still alert”- Single scheduler thread. By default, Spring runs scheduled tasks on one
thread, so a slow task delays the others. Raise
spring.task.scheduling.pool.sizeif tasks queue up. - Scaled to zero. A platform that stops idle instances stops their schedules too.
- Timezone mismatch. The check and the
zoneattribute must agree.
If alerts still don’t match what you expect, see troubleshooting monitoring.