Computer scienceBackendSpring BootAsynchronous processing

Scheduling

8 minutes read

In Spring, scheduling can be used to create tasks that run for a specific period of time. There are different reasons why you might need to use it in your app. One example would be if your app contains a messaging feature: in order to check if a new message is received, a client would need to query the server. This task can be scheduled to be executed at a fixed interval so that the client will automatically check for new messages every few seconds. In this topic, you will learn how to schedule tasks in Spring according to your needs.

Creating a basic schedule

To enable a schedule in your Spring application, you need to use the @EnableScheduling annotation. This annotation will be added to the top of the class that runs the scheduled tasks.

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.*;

@SpringBootApplication
@EnableScheduling
public class Demo2Application {
    public static void main(String[] args) {
        SpringApplication.run(Demo2Application.class, args);
    }
}
Kotlin
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.scheduling.annotation.*

@SpringBootApplication
@EnableScheduling
class Demo2Application

fun main(args: Array<String>) {
    runApplication<Demo2Application>(*args)
}

Once this annotation is added, it is possible to create scheduled tasks using the @Scheduled annotation. One execution of a method annotated with @Scheduled is one task.

The @Scheduled annotation has three main scheduling modes. The first is fixedRate, which executes a task at a set time interval. The time interval is measured in milliseconds, so 1 second is represented as 1000. For example, if you wanted to create a task that prints "Hello" every 2 seconds, you could do so using the code below.

Java
import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.*;

@Component
public class GreetScheduler{
	
    @Scheduled(fixedRate = 2000)
    public void greetUser() {
        System.out.println("Hello!");
    }
}
Kotlin
import org.springframework.stereotype.Component
import org.springframework.scheduling.annotation.*

@Component
class GreetScheduler {
    @Scheduled(fixedRate = 2000)
    fun greetUser() {
        println("Hello!")
    }
}

The second mode is fixedDelay, which executes a task after a set delay, then continues executing it at the same interval. The difference between fixedDelay and fixedRate is that fixedDelay executes the task after a set delay once the previous one is completed. With fixedRate, the duration of the delay doesn't change and the task is run immediately after the previous one is executed. For instance, if you wanted to create a task that is completed 2 seconds after the application starts, and every 2 seconds after the initial print, you could do so using the code below.

Java
@Component
public class ImportantTaskScheduler {
	
    @Scheduled(fixedDelay = 2000)
    public void runImportantTask() {
        System.out.println("Task started!");
    }
}
Kotlin
@Component
class ImportantTaskScheduler {
    @Scheduled(fixedDelay = 2000)
    fun runImportantTask() {
        println("Task started!")
    }
}

Finally, initialDelay can be used to specify the delay for the first run of a task. This is typically paired with fixedDelay specifying the delay for every task that follows the first task. For example, when creating a task that checks for new messages 2 seconds after the launch, and with the delay of 1 second each subsequent time, you can use the code below.

Java
@Component
public class MessageScheduler {
	
    @Scheduled(initialDelay = 2000, fixedDelay = 1000)
    public void checkForMessage() {
        System.out.println("New message received!");
    }
}
Kotlin
@Component
class MessageScheduler {
    @Scheduled(initialDelay = 2000, fixedDelay = 1000)
    fun checkForMessage() {
        println("New message received!")
    }
}

It is important to understand that if you have a field in a scheduler component, e.g. a collection of elements, and you are going to modify it, you need to ensure a correct synchronization because your code works in a multithreading environment.

Asynchronous scheduling

When we use fixedRate, the method won't be invoked again until the previous execution is finished. However, we may sometimes want to support parallel execution of the same method. To achieve this, we need to make the tasks run asynchronously, independently from each other. In Spring, the @Async annotation can be added to any scheduled task for it to be completed asynchronously.

Java
@Component
@EnableAsync // enables async methods
public class AsyncTaskScheduler {

    @Async
    @Scheduled(fixedRate = 1000)
    public void asyncTask() throws InterruptedException {
        System.out.println("Task started!");
        Thread.sleep(2000);
        System.out.println("Task completed!");
    }
}
Kotlin
@Component
@EnableAsync // enables async methods
class AsyncTaskScheduler {
    
    @Async
    @Scheduled(fixedRate = 1000)
    fun asyncTask() {
        println("Task started!")
        Thread.sleep(2000)
        println("Task completed!")
    }
}

This code will start the method every 1000 milliseconds even if the previous execution (or task) hasn't been finished. The program will print something similar to the output below:

Task started!
Task started!
Task completed!

It means that the method had invoked the second time before the first execution completed.

It is important to note that the @Async annotation should only be used with fixedRate scheduling. This is because fixedDelay executes dependent on the previous task, meaning it is always synchronous with other tasks, rather than asynchronous.

Cron jobs and macros

To learn more about cron job syntax, check the official documentation.

In addition to scheduling based on rates, it is possible to schedule jobs in Spring using cron. To do this, we will use the same @Scheduled annotation, except we will use the cron argument to specify our schedule. For example, if we wanted to schedule a task to run at 7:00 PM every day, we could use the 0 0 19 * * ? cron within the @Scheduled annotation as shown below.

Java
@Component
public class UpdateScheduler {
	
    @Scheduled(cron="0 0 19 * * ?")
    public void dailyUpdate() {
        System.out.println("Downloading new updates!");
    }
}
Kotlin
@Component
class UpdateScheduler {

    @Scheduled(cron = "0 0 19 * * ?")
    fun dailyUpdate() {
        println("Downloading new updates!")
    }
}

To understand this cron statement, let's break it down by character. The first two zeros indicate the seconds and minutes, and 19 represents the hour. So, the first three characters indicate that the cron job executes at hour 19 (which is 7 PM in the 24-hour time format), minute 0 second 0. The last three characters represent the month, the day of the week, and the year. The * character indicates that the task has to run any day of the month, as well as any month. The question mark indicates that the last two arguments, the day of the week and the year, are not provided. Overall, the task will execute at 7:00 PM every day when the program is running.

Starting with Spring 5.3, there are now macros to help simplify common cron expressions. For example, if you want a task to run daily, you can just use the @dailyannotation as shown below.

Java
@Component
public class BackupScheduler {
	
    @Scheduled(cron="@daily")
    public void performDailyBackup() {
        System.out.println("Backing up data!");
    }
}
Kotlin
@Component
class BackupScheduler {
    
    @Scheduled(cron = "@daily")
    fun performDailyBackup() {
        println("Backing up data!")
    }
}

There are 5 commonly used macros: @yearly, @monthly, @weekly, @daily, and @hourly. These save you some work and make your code more readable for common expressions.

Conclusion

Scheduling is a method that can be used to create tasks that occur on a set schedule. It allows us to create tasks that repeat at set intervals, with set delays, or with an initial delay. We can also create more complex schedules using cron, which can define specific dates and times for tasks to execute. Finally, we can make tasks run asynchronously using the @Async annotation, which can allow for multiple tasks running at the same time.

31 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo