Dynamic Spring Boot Cron Jobs Schedule Tasks

{getToc} $title={Table of Contents}

Dynamic Spring Boot Cron Jobs


Introduction

In this blog, I will do the implementation by creating a new spring boot project in order to demo how to create the dynamic cron jobs scheduling by useing Spring Boot.

Firstly, we'll set up project from start.spring.io to build a spring boot maven project from scratch.

set up spring boot maven project

We need 2 dependencies only for doing this demo project about dynamic cron scheduling tasks with spring boot.


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.4</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>yuthads.blogspot.com</groupId>
	<artifactId>dynamiccrontasks</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>dynamiccrontasks</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>


To use cron schedule tasks in Spring Boot projects, you can use cron expressions and define them in advance in the configuration file. You cannot dynamically modify the task execution time during project operation, which is very inflexible.

I'm going to introduces 2 ways in order to create dynamic Spring Boot cron jobs schedule sasks.

1. Cron expressions

A cron expression is a string of six to seven fields separated by white space to represent triggers on the second, minute, hour, day of the month, month, day of the week, and optionally the year. However, the cron expression in Spring Scheduler can be as simple as * * * * ? * or as complex as 0 0/5 14,18,3-39,52 ? JAN,MAR,SEP MON-FRI 2002-2010.

I'm going to create a schedule timer in one file called: task-scheduling.ini

scheduling.cron=0/5 * * * * ?

This mean the interval of cron running is in every 5 seconds.

In application.properties file, I have set server port 8080 to run spring boot application on that port.

server.port=8080

Cron Expression scheduled task execution class:


package yuthads.blogspot.com.dynamiccrontasks.task.scheduling;

import java.time.LocalDateTime;
import java.util.Date;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Data
@Slf4j
@Component
@PropertySource("classpath:/task-scheduling.ini")
public class CronExpressionScheduleTask implements SchedulingConfigurer {
	
	@Value("${scheduling.cron}")
	private String cron;
	
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
	    taskRegistrar.addTriggerTask(new Runnable() {
			
	        @Override
		public void run() {
		    log.info("Current time: {}", LocalDateTime.now());
			}
		}, new Trigger() {

		@Override
		public Date nextExecutionTime(TriggerContext triggerContext) {
		CronTrigger cronTrigger = new CronTrigger(cron);
		Date nextExecutionTime = cronTrigger.nextExecutionTime(triggerContext);
		return nextExecutionTime;
			}
		});
	}
}



I'm going to create a controller in order to create an end-point, so that the execution time of the scheduled task can be dynamically modified by calling that api end-point:

package yuthads.blogspot.com.dynamiccrontasks.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import yuthads.blogspot.com.dynamiccrontasks.task.scheduling.CronExpressionScheduleTask;

@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/scheduling")
public class TaskSchedulingCntr {

	private final CronExpressionScheduleTask taskScheduling;
	
	@GetMapping("/update_cron")
	public ResponseEntity<String> updateCron(String cron) {
		
	    log.info("Current cron schedule: {}", cron);
	    taskScheduling.setCron(cron);
		
	    return new ResponseEntity<>("ok",HttpStatus.OK);
	}
}

Before you run the project, don't forget to enable spring scheduling annotation. Scheduling is not enabled by default. Before adding any scheduled jobs we need to enable scheduling explicitly by adding the @enableScheduling annotation:

package yuthads.blogspot.com.dynamiccrontasks;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class DynamiccrontasksApplication {

	public static void main(String[] args) {
	    SpringApplication.run(DynamiccrontasksApplication.class, args);
	}
}

Let's running project as Spring Boot App. After we have successfully started up the project, then we'll get this kind of logging.

y.b.c.d.DynamiccrontasksApplication      : The following 1 profile is active: "dev"
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
o.apache.catalina.core.StandardService   : Starting service [Tomcat]
org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1132 ms
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
y.b.c.d.DynamiccrontasksApplication      : Started DynamiccrontasksApplication in 2.155 seconds (JVM running for 3.012)
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:26:20.005919800
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:26:25.004856500
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:26:30.006997200
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:26:35.016202600
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:26:40.015805500
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:26:45.013416600

The schedule time interval would be in every 5 seconds as we have configured in the task-scheduling.ini file.

Then Let's try to modify cron schedule timer by calling the end-point which we have already created in the Scheduling Controller class by using postman tools.
Let's pass in the request parameter cron expression, and modify the scheduled task to execute once every 10 seconds
We'll set the cron expression to 0/10 * * * * ?.



http://localhost:8080/scheduling/update_cron?cron=0/10 * * * * ?

Let's see in the logging. Now cron schedule have trigger in every 10 seconds.

y.b.c.d.controller.TaskSchedulingCntr    : Current cron schedule: 0/10 * * * * ?
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:34:05.002866500
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:34:10.011560600
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:34:20.008959500
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:34:30.013743100
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:34:40.001236500
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:34:50.016377
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:35:00.004262900
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:35:10.004201
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:35:20.007959100
y.b.c.d.t.s.CronExpressionScheduleTask   : Current time: 2022-10-07T14:35:30.010241700


2. Set Dynamic Scheduling using Trigger

In addition to the above method with the help of cron expressions, there is another trigger, which is different from the CronTrigger trigger. This trigger can set the cycle interval time at will, unlike the cron expression, which can only define an interval less than or equal to 59 seconds.

package yuthads.blogspot.com.dynamiccrontasks.task.scheduling;

import java.time.LocalDateTime;
import java.util.Date;

import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.stereotype.Component;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Data
@Slf4j
@Component
public class TriggerScheduleTask implements SchedulingConfigurer {
	
	private Long timer = 10000L;
	
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

		taskRegistrar.addTriggerTask(new Runnable() {
			
		@Override
		public void run() {
		    log.info("Current trigger time: {}", LocalDateTime.now());
		    }
		}, new Trigger() {
			
		@Override
		public Date nextExecutionTime(TriggerContext triggerContext) {

		PeriodicTrigger periodicTrigger = new PeriodicTrigger(timer);
		Date nextExecutionTime = periodicTrigger.nextExecutionTime(triggerContext);
				
		return nextExecutionTime;
			}
		});
	}
}


Let's create an other end-point, so that the execution time of the scheduled task can be dynamically modified by calling that api end-point:

package yuthads.blogspot.com.dynamiccrontasks.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import yuthads.blogspot.com.dynamiccrontasks.task.scheduling.TriggerScheduleTask;

@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/scheduling")
public class TaskSchedulingCntr {

	private final TriggerScheduleTask triggerScheduleTask;
	
	@GetMapping("/update_timer")
	public ResponseEntity<String> updateTimer(Long timer) {
		
		log.info("Current timer schedule: {}", timer);
		triggerScheduleTask.setTimer(timer);
		
		return new ResponseEntity<>("ok",HttpStatus.OK);
	}
}


Let's run project again, and check in the logging after successfully started up project, then you will get schedule interval will be trigger at every 10 seconds as we have set the timer = 10000L. It's mean 10 seconds.

private Long timer = 10000L;


Logging.

o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:48:57.974380700
y.b.c.d.DynamiccrontasksApplication      : Started DynamiccrontasksApplication in 2.058 seconds (JVM running for 3.276)
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:49:07.987791
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:49:17.990261400
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:49:27.995728700
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:49:38.003339
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:49:48.011247900
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:49:58.013432900
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:50:08.014068600

Lastly, let's modify the trigger schedule timer dynamically by using the controller end-point by using postman tools again.


Let's set schedule timer to trigger cron at every 15 seconds.

http://localhost:8080/scheduling/update_timer?timer=15000

Now, let's check the loggint. That's will be trigger cron at every 15 seconds.

y.b.c.d.controller.TaskSchedulingCntr    : Current timer schedule: 150000
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:55:18.230800
y.b.c.d.controller.TaskSchedulingCntr    : Current timer schedule: 15000
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:57:48.242307200
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:58:03.258612600
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:58:18.268559500
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:58:33.282879400
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:58:48.292707200
y.b.c.d.t.s.TriggerScheduleTask          : Current trigger time: 2022-10-07T14:59:03.297736800


That's it.

Conclusion

Hope this article was helpful.

Cron runs as a daemon process. This means it only needs to be started once and it will keep running in the background. This process makes use of crontab to read the entries of the schedules and kicks off the tasks.



Previous Post Next Post