Schedule Tasks Programmatically in Spring Boot

Posted on Sep 14, 2023 in
Reading time: 3 minutes

Intro

Scheduling tasks in Spring can easily be done using the @Scheduled annotation, which offers a great deal of flexibility. However, sometimes you need to schedule tasks programmatically, for example when you want to schedule a task based on user input.

In this tutorial we take a look at how to schedule tasks using the TaskScheduler interface.

🍿 Watch on YouTube or get the code from GitHub

Let’s Code

The TaskScheduler is the central interface to schedule tasks in Spring. It offers methods for scheduling at a fixed rate, with a fixed delay, or, based on a trigger.

To get the scheduler from the application context, we can simply inject it into our class. But we have to make sure to use @EnableScheduling since otherwise it will not be available.

 ...
package dev.axgr
 
import org.slf4j.LoggerFactory
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.scheduling.TaskScheduler
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.support.CronTrigger
import java.time.Duration
import java.util.concurrent.TimeUnit
 
@EnableScheduling
@SpringBootApplication
class App {
 ...
companion object {
private val log = LoggerFactory.getLogger(App::class.java)
}
 
@Bean
fun run(scheduler: TaskScheduler) = CommandLineRunner {
val one = scheduler.scheduleAtFixedRate({ log.info("Job #1 working..") }, Duration.ofSeconds(1))
Thread.sleep(TimeUnit.SECONDS.toMillis(3))
one.cancel(true)
}
 
}
 ...
fun main(args: Array<String>) {
runApplication<App>(*args)
}
 

With the scheduler at hand, we can schedule a simple logging task that is executed every second. We also get back a refernce to the task, a ScheduledFuture, which we can use to cancel the task after about 3 seconds.

Scheduling a task with a fixed delay works pretty mucht the same; so let’s take a look at scheduling a task using a cron expression.

 ...
package dev.axgr
 
import org.slf4j.LoggerFactory
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.scheduling.TaskScheduler
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.support.CronTrigger
import java.time.Duration
import java.util.concurrent.TimeUnit
 
@EnableScheduling
@SpringBootApplication
class App {
 ...
companion object {
private val log = LoggerFactory.getLogger(App::class.java)
}
 
@Bean
fun run(scheduler: TaskScheduler) = CommandLineRunner {
val one = scheduler.scheduleAtFixedRate({ log.info("Job #1 working..") }, Duration.ofSeconds(1))
Thread.sleep(TimeUnit.SECONDS.toMillis(3))
one.cancel(true)
 
val cron = CronTrigger("* * * * * *")
val two = scheduler.schedule({ log.info("Job #2 working..") }, cron)
 
Thread.sleep(TimeUnit.SECONDS.toMillis(3))
two?.cancel(true)
 
}
 
}
 ...
fun main(args: Array<String>) {
runApplication<App>(*args)
}
 

The schedule method takes the runnable and an implementation of the Trigger interface. That interface is used to calculate the next execution time. One such implementation is the CronTrigger which can be initialized using a String.

If we want the job to run every second, we can pass * * * * * * as the cron pattern.

This starts the job and again, after about 3 seconds we cancel that job as well. Note that the reference to the second task may be null, in case the trigger never fires. So we have to use a safe call for the cancellation.

The Dead Letter Queue

Love what you're seeing? By subscribing to my newsletter, not only will you be the first to know about fresh tutorials and videos, but you'll also unlock:

Subscribe now and become a part of our growing tech tribe!