Skip to content
Get started

How to monitor Spring @Scheduled tasks

To monitor Spring @Scheduled tasks, expose Spring Boot Actuator's GET /actuator/scheduledtasks endpoint to inspect each task's next and last execution times and status (STARTED, SUCCESS, or ERROR). For deeper observability, enable Spring's @EnableScheduling with Micrometer metrics so each task duration and error rate appears in your metrics backend. Add a heartbeat ping at the end of each important task so an external monitor can alert you when an expected execution doesn't happen.

How do I use the Actuator scheduledtasks endpoint?

Add the Actuator dependency and expose the scheduledtasks endpoint. It returns each task's schedule expression, last execution time, and status:

application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,scheduledtasks
  endpoint:
    scheduledtasks:
      enabled: true
curl -s http://localhost:8080/actuator/scheduledtasks | python3 -m json.tool
# Returns: cron tasks, fixedRate tasks, fixedDelay tasks — each with lastExecution status

The lastExecution block includes status: SUCCESS, ERROR, or STARTED (still running). Poll this endpoint and alert when a task's status is ERROR or its lastExecution timestamp is older than expected.

How do I add a heartbeat to a Spring scheduled task?

Ping an external monitor at the end of the scheduled method. Spring only calls this method when the scheduler is running, so a missed ping catches a scheduler that stopped or a task that threw:

NightlyReportTask.java
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;

@Component
public class NightlyReportTask {

    private static final String PING_URL = "https://ping.cronshield.com/<your-check-id>";

    @Scheduled(cron = "0 0 3 * * *")  // 03:00 every day
    public void runNightlyReport() {
        doTheWork();
        // Report success last. An exception above skips this call.
        try {
            HttpClient.newHttpClient().send(
                HttpRequest.newBuilder(URI.create(PING_URL)).GET().build(),
                HttpResponse.BodyHandlers.discarding()
            );
        } catch (Exception ignored) {}
    }
}
PING_URL is a placeholder for the endpoint you get when you create a monitor. The CronShield receiver ships in an upcoming release.

How does Micrometer observability help?

Spring Framework 6.1+ adds Micrometer Observation support to @Scheduled tasks. Each execution emits a timer metric (spring.scheduling.task) with outcome=SUCCESS or outcome=ERROR tags. Export these to Prometheus or your metrics backend and alert on a non-zero error rate or on a task that hasn't executed in longer than its schedule interval.

Add a missed-run alert to this job

The free tier gives you a heartbeat endpoint and an email alert when an expected ping doesn't arrive. Paid tiers add the log-aware diagnosis — the last log line and a likely cause in the alert. The heartbeat receiver ships in an upcoming release; see the plans to learn what each tier adds.

Frequently asked questions

Does Spring retry a @Scheduled task that threw an exception?
No. By default a @Scheduled method that throws an exception logs the error and does not retry. The next scheduled execution fires at the normal time. To add retries, use Spring Retry's @Retryable annotation or handle exceptions inside the method.
What happens to @Scheduled tasks during a rolling deploy?
If a task is mid-execution when the pod is replaced, it is interrupted without completing. The next execution fires on the new pod on schedule. Add a heartbeat to detect the gap, and set a grace period wider than your deployment window.