One of the useful features of the Spring Framework is the ability to perform scheduling to ensure that certain methods are called at specific intervals.
This feature can be very useful in situations where you need to perform a task repeatedly, such as generating statistics every hour, cleaning up temporary files at night, or performing backups once a day.
Scheduling Annotations and @EnableScheduling
The built-in scheduling is enabled via @EnableScheduling, which configures a ScheduledAnnotationBeanPostProcessor that looks at certain annotations and calls the marked methods at repeated intervals. As usual, the enabler must be at any @Configuration:
@Configuration
@EnableScheduling
class SchedulingConf { }
Because @SpringBootApplication is a special @Configuration, @EnableScheduling would be allowed there as well.
@Scheduled
Methods that are to be called repeatedly are annotated with @Scheduled. Annotation attributes determine in which frequency the method should be called. The basic structure is as follows:
@Component
class Repeater {
@Scheduled( … )
public void repeatMe() { }
}
Two things need to be considered:
- The method must not have a parameter list. Spring calls the method and passes nothing to the method; auto-wiring of parameters is also not possible.
- The method doesn’t return anything because there’s nowhere for the results to go.
Scheduled with fixedDelay or fixedRate
There are different annotation attributes to determine the repeat frequency for a method such as repeatMe(). With fixedDelay, Spring calls the method and then waits for the specified time unit (by default, this is milliseconds), which are specified with fixed-Delay:
@Scheduled( fixedDelay = 1000 /* ms */ )
public void wait1SecondAfterCallThenRepeat() { }
Spring will wait for one second after the method execution and then call the method again. Whether the method finishes quickly or not doesn’t matter: the interval between method calls is always one second (ideal). The situation is different with the next option.
With fixedRate, Spring calls the method periodically like a metronome:
@Scheduled( fixedRate = 1000 /* ms */ )
public void calledEverySecondMaybeParallel() { }
In this example, Spring will call the method every second. The consequence could be that the method runs several times at the same time—for example, if the method takes two seconds. Undeterred, Spring will call the method again every second.
Using fixedDelay and fixedRate like this, the processing starts immediately. But an initialDelay can defer the initial call:
@Scheduled( initialDelay = 1000 /* ms */, fixedRate = 5000 /* ms */ )
public void wait1SecondThenRepeadEvery5Seconds() { }
In the example, an initial delay of one second is set.
The time units can also be changed via the timeUnit attribute, and the unit is of type java.util.concurrent.TimeUnit.
The next possibility to specify the repetition frequency are cron expressions.
Cron Expressions
CronJobs complete tasks under Unix systems at fixed times in the background. In the core there are six segments for a setting or repetition, namely in the accuracy: seconds, minutes, hours, day of the month, month, and day of the week. Schematically, it looks like this:
┌───────────── Seconds (0-59)
│ ┌───────────── Minutes (0-59)
│ │ ┌───────────── Hours (0-23)
│ │ │ ┌───────────── Day of the month (1-31)
│ │ │ │ ┌───────────── Month (1-12)
│ │ │ │ │ ┌───────────── Day of the week (0-7)
│ │ │ │ │ │
│ │ │ │ │ │
* * * * * *
3
Here are three examples:
- * * *: every minute
- 0 * * *: every hour
- */10 * * *: every 10 seconds
The CronJob syntax is complex, but websites such as https://crontab-generator.org/ or https://crontab.guru/ help with “piecing together” the expression.
With the @Scheduled annotation, a cron expression can be set using the cron annotation attribute. Here’s an example:
// second, minute, hour, day-of-month, month, day(s) of week
@Scheduled( cron = "*/5 * * * * MON-FRI" )
public void cronEveryFiveMinutesWorkingDays() { }
The cron expression causes the method to be called every five minutes from Monday to Friday. zone can also be used to specify a time zone.
Because cron expressions aren’t that easy to read, Spring has introduced special macros that can express typical repetitions:
@Scheduled( cron = "@hourly" )// @daily/@midnight,@weekly,@monthly,@yearly public void cronMacro() { }
Details and examples are available at https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html.
@Scheduled Annotation Type
The @Scheduled annotation type is declared as follows:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
String CRON_DISABLED = ScheduledTaskRegistrar.CRON_DISABLED;
String cron() default "";
String zone() default "";
long fixedDelay() default -1;
String fixedDelayString() default "";
long fixedRate() default -1;
String fixedRateString() default "";
long initialDelay() default -1;
String initialDelayString() default "";
}
We’ve already talked about fixedDelay, fixedRate, initialDelay, cron, and zone. Interestingly, for *delay and *rate, there are two ways to specify them: once via a number of type long and then via a SpEL expression as a String. SpEL can address external properties in a configuration file, for example, and this is useful. For example, you can control that in one case there are no repetitions, but in another case, there are.
Disadvantages of @Scheduled and Alternatives
The @Scheduled annotation sounds helpful, but its capabilities are comparatively low, and there are drawbacks as well:
- Once Spring has started a background thread for execution, the timer runs forever. While initialDelay can delay execution, there is no (easy) way to specify the number of repetitions or to stop the timer later.
- CronJobs can be bound to fixed times. This is a problem if, for example, a method should be called at 12 o’clock at night to delete the temporary folder, but one second before 12, the server crashes, and shortly after 12 o’clock, the system is active again: the system misses the time of the CronJob due to this incident, and consequently the operation doesn’t run.
Note: Outlook: The scheduling feature in the Spring Framework has been a topic of discussion among developers. Some have questioned why the scheduling capability in Spring seems limited compared to other frameworks. One possible explanation is that Spring is designed as an integration framework, meaning it provides a unified platform for integrating various technologies, rather than trying to offer a comprehensive solution for every feature in its own library. Scheduling requires more than just a simple annotation; it involves state and transaction management, among other things. As a result, many developers turn to third-party libraries such as Quartz (www.quartz-scheduler.org/) for more robust scheduling functionality. Quartz is an open-source library for scheduling jobs in Java, which is widely used in enterprise applications and is backed by Software AG.
Editor’s note: This post has been adapted from a section of the book Spring Boot 3 and Spring Framework 6 by Christian Ullenboom.
Comments