Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Scheduled tasks

Some jobs an application should do on its own, without a request: count new posts every morning, clean old data every hour, send emails at night. Spring can call a bean method on a schedule. Two annotations are needed: one switches the scheduler on, the other says when to call the method.

Switching it on

// src/main/java/com/example/blog/AppConfig.java
@Configuration
@EnableCaching
@EnableScheduling
public class AppConfig {
 
    @Bean
    public Clock clock() {
        return Clock.systemUTC();
    }
}

@EnableScheduling turns the scheduler on. We put it in AppConfig rather than on the main class — the @DataJpaTest lesson explains why. @EnableCaching is needed in the next lesson.

The task

// src/main/java/com/example/blog/DigestJob.java
package com.example.blog;
 
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
@Component
public class DigestJob {
 
    private static final Logger log = LoggerFactory.getLogger(DigestJob.class);
 
    private final PostRepository posts;
    private final Clock clock;
 
    public DigestJob(PostRepository posts, Clock clock) {
        this.posts = posts;
        this.clock = clock;
    }
 
    @Scheduled(cron = "${blog.digest-cron}")
    public void logDailyDigest() {
        Instant dayAgo = Instant.now(clock).minus(Duration.ofDays(1));
        long count = posts.countByCreatedAtAfter(dayAgo);
        log.info("Posts in the last 24 hours: {}", count);
    }
}

The method counts the posts of the last day with the countByCreatedAtAfter query (add it to PostRepository) and writes the result to the log. A @Scheduled method takes nothing and returns nothing. The schedule comes from a setting:

# src/main/resources/application.yaml
blog:
  digest-cron: "0 0 9 * * *"

A cron expression

In Spring a cron expression has six fields:

FieldSecondsMinutesHoursDay of monthMonthDay of week
0 0 9 * * *009anyanyany
*/10 * * * * *every 10anyanyanyanyany
0 0 3 * * MON003anyanyMonday

The first expression is every day at 9:00, the second every 10 seconds. Time follows the server's time zone; another zone is set with the zone = "Europe/Berlin" parameter. The quotes in YAML are needed because of the asterisks.

Checking it

No need to wait until nine in the morning — pass a frequent schedule at startup:

./mvnw spring-boot:run -Dspring-boot.run.arguments="--blog.digest-cron=*/10 * * * * *"
INFO ... [   scheduling-1] com.example.blog.DigestJob : Posts in the last 24 hours: 1
INFO ... [   scheduling-1] com.example.blog.DigestJob : Posts in the last 24 hours: 2

The task ran twice, 10 seconds apart; a post was created in between. The thread name scheduling-1 shows the method runs not in a request thread but in the scheduler's thread.

Without cron

ParameterMeaning
fixedRate = 60000Every 60 seconds from the start of the previous run
fixedDelay = 6000060 seconds after the previous run finished
initialDelay = 5000The first run 5 seconds after startup

There is also a timeUnit parameter: @Scheduled(fixedRate = 1, timeUnit = TimeUnit.HOURS) — once an hour.

Common mistake

If you forget @EnableScheduling, @Scheduled methods are simply never called — no error, no warning. If a task stays silent, check the annotation and that the task class is a bean (@Component). Keep another thing in mind: when the application runs on two servers, the task fires on both. Such cases use a lock in the database, for example the ShedLock library.

Thanks for reading!

Donate to the site — we work hard for you.