Scheduled jobs
Some work an application must do on its own, without a request: collect statistics every minute, clean up old drafts at night, send a digest every hour. Micronaut has the @Scheduled annotation for this. The framework runs a method marked with it on a schedule, in a separate thread pool. Nothing needs to be added: the scheduler is part of the core.
The first job
Create the class by hand or with mn create-job com.example.StatsJob:
// src/main/java/com/example/StatsJob.java
package com.example;
import io.micronaut.scheduling.annotation.Scheduled;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class StatsJob {
private static final Logger LOG = LoggerFactory.getLogger(StatsJob.class);
private final ArticleRepository articleRepository;
public StatsJob(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
@Scheduled(fixedDelay = "${blog.stats-interval:1m}", initialDelay = "5s")
void logStats() {
LOG.info("Articles in the blog: {}", articleRepository.count());
}
}The job is an ordinary bean: it gets the repository through its constructor. initialDelay = "5s" means the first run happens 5 seconds after startup, then the pause comes from the blog.stats-interval setting, or one minute if it is missing. Durations are written as 10s, 1m, 2h.
Trying it out
To see it quickly, shorten the interval in application.properties:
# src/main/resources/application.properties
blog.stats-interval=10sStart the application, create an article and watch the log:
12:10:52.113 [scheduled-executor-thread-1] INFO com.example.StatsJob - Articles in the blog: 1
12:11:02.114 [scheduled-executor-thread-2] INFO com.example.StatsJob - Articles in the blog: 1The lines appear every 10 seconds. The thread name scheduled-executor-thread shows that the job does not run on an HTTP request thread.
Kinds of schedules
| Parameter | How it works |
|---|---|
fixedDelay = "1m" | A 1-minute pause after the previous run ends |
fixedRate = "1m" | A run every minute from the start of the previous one |
cron = "0 0 3 * * *" | By a cron expression: every day at 03:00 |
initialDelay = "5s" | A delay before the first run |
A cron expression in Micronaut has six fields: seconds, minutes, hours, day of month, month, day of week. A nightly job looks like this:
// src/main/java/com/example/StatsJob.java
@Scheduled(cron = "0 0 3 * * *")
void nightly() {
LOG.info("Nightly cleanup");
}If a job can take longer than its interval, choose fixedDelay: the runs will not overlap.
Common mistake
A @Scheduled method runs only if its class is a bean. If you forget @Singleton, the application starts without errors or warnings, but the job never runs: its lines simply never appear in the log. When a job stays "silent", first check the scope annotation on the class.