Let's get started with a Microservice Architecture with Spring Cloud:
Getting a Cron Expression From Database for a Spring Boot Scheduled Job
Last updated: June 3, 2026
1. Overview
Spring Boot allows us to easily schedule a job, for example, using the @Scheduled annotation. Sometimes, we don’t want to hardcode the scheduling parameters, such as the cron expression. Instead, we would like to schedule a job using a cron expression from a database.
In this tutorial, we’ll first explore two approaches to achieve it. Further, we’ll discuss when to use which.
2. Introduction to the Problem
When we work with Spring scheduling, the most straightforward option is usually to place the cron expression directly in @Scheduled, for example:
@Scheduled(cron = "*/5 * * * * ?")
public void run() {
// job logic
}
This works well, but it also hardcodes the schedule into the application.
The problem appears when we want to fetch the cron expression from a database. This is because we cannot simply write something like this:
private String cronExpression = loadFromDatabase();
@Scheduled(cron = cronExpression)
public void run() {
// invalid approach
}
If we compile this code, the compiler will complain:
java: element value must be a constant expression
The reason is that the cron attribute in an annotation cannot be populated from an arbitrary runtime variable. In practice, we either need an indirection that Spring can resolve for us, or we need to move away from annotation-driven scheduling altogether.
In this tutorial, we’ll explore both approaches. But let’s do some preparation before we dive into the solutions.
3. Preparing Data in a Database
For this tutorial, we use a small H2 in-memory database. Also, for simplicity, we’ll skip Spring Data JPA configurations
3.1. Creating a Simple Table
We want our Spring Boot application to automatically create a database table and insert some data into it when it starts. Therefore, let’s prepare a schema.sql file:
CREATE TABLE IF NOT EXISTS cron_config (
id BIGINT PRIMARY KEY,
cron_expression VARCHAR(255) NOT NULL
);
As we can see, the cron_config table is intentionally minimal. We only need an identifier and the cron expression itself.
Next, let’s create a data.sql file to insert a row into the table:
INSERT INTO cron_config (id, cron_expression) VALUES (1, '*/5 * * * * ?');
We insert a single row so the application has a cron value as soon as it starts. So by default, the schedule runs every five seconds.
3.2. Preparing the Entity and the Repository Classes
The corresponding JPA entity is equally small:
@Entity
@Table(name = "cron_config")
public class CronEntity {
@Id
private Long id;
private String cronExpression;
// ... the default constructor, getters and setters are omitted
}
The repository is just as simple and gives us the CRUD operations we need:
public interface CronConfigRepository extends JpaRepository<CronEntity, Long> {
}
With these pieces in place, we can read and update cron values from the database.
Now, let’s see how to tell Spring to schedule jobs using the cron expression we stored in the database.
4. Using a cronLoader Bean
In our first approach, we would like to keep the @Scheduled annotation, but avoid placing the cron expression directly in the annotation. Instead, we’ll create a Spring Bean to read the cron expression from our database and reference the bean in @Scheduled’s cron attribute.
4.1. Defining the cronLoader Bean to Load the Cron Expression
We start with a small configuration class that defines a bean named cronLoader:
@Configuration
public class CronLoaderConfig {
@Bean
String cronLoader(CronConfigRepository repository) {
return repository.findById(1L)
.map(CronEntity::getCronExpression)
.orElseThrow(() -> new RuntimeException("Cron expression not found in DB"));
}
}
As the code above shows, this bean is pretty straightforward. It reads the cron expression from the row with id = 1 and returns it as a String.
4.2. Referencing the Bean from @Scheduled
Next, we can reference that bean through SpEL in our scheduled job:
@Component
public class AnnotationScheduledJob {
private static final Logger log = LoggerFactory.getLogger(AnnotationScheduledJob.class);
@Scheduled(cron = "#{@cronLoader}")
public void run() {
log.info("✅ [{}] Job executed - cron loaded from DB via @Scheduled", now());
}
}
This is the key idea: we still use @Scheduled, but the cron expression comes from the cronLoader bean instead of being embedded directly in the annotation.
Also, our job does nothing but print a line with the execution time, so we can verify whether the executions match our cron expression in the database.
Next, let’s verify if the job scheduling works as we expected.
4.3. Starting the Application and Observing the Console Outputs
If we start our Spring Boot application, we can see these lines in the console log:
...
... AnnotationScheduledJob : ✅ [10:00:05.002] Job executed - cron loaded from DB via @Scheduled
... AnnotationScheduledJob : ✅ [10:00:10.001] Job executed - cron loaded from DB via @Scheduled
... AnnotationScheduledJob : ✅ [10:00:15.000] Job executed - cron loaded from DB via @Scheduled
... AnnotationScheduledJob : ✅ [10:00:20.001] Job executed - cron loaded from DB via @Scheduled
...
Because our data.sql inserts “*/5 * * * * ?” as the cron expression, we see the annotation-based job log roughly every five seconds. This gives us a straightforward way to verify that the cron value is being loaded from the database.
4.4. Limitation of the cronLoader Bean Approach
This approach is convenient, but it comes with an important limitation.
Spring loads the cron value when it creates the cronLoader bean during application startup. After that, the scheduled method continues using the resolved value.
If we update the cron_config table later, the schedule won’t automatically change for the running application. In practice, we would typically need to restart the application to pick up the new value.
So, this approach is a good fit only when loading the schedule once at startup is acceptable.
5. Using Spring’s SchedulingConfigurer
If we want our scheduled jobs to react to database changes without restarting, we need a more dynamic solution. Next, let’s dive in.
5.1. Implementing SchedulingConfigurer
Instead of relying on the @Scheduled annotation, we can implement Spring’s SchedulingConfigurer and register our own trigger task.
First, let’s create a DynamicScheduledConfig configuration class implementing SchedulingConfigurer:
@Configuration
public class DynamicScheduledConfig implements SchedulingConfigurer {
private static final Logger log = LoggerFactory.getLogger(DynamicScheduledConfig.class);
private final CronConfigRepository repository;
public DynamicScheduledConfig(CronConfigRepository repository) {
this.repository = repository;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(() -> log.info(
"✅ [{}] DynamicScheduledConfig executed - cron re-read from DB per execution",
now()), triggerContext -> {
String cronExpression = repository.findById(1L)
.map(CronEntity::getCronExpression)
.orElseThrow(() -> new RuntimeException("Cron expression not found in DB"));
return new CronTrigger(cronExpression).nextExecution(triggerContext);
});
}
}
Next, let’s understand how the configureTasks() method works:
First, we register a task with the addTriggerTask(…) method. The addTriggerTask() method has two parameters: a Runnable (the “What”) that contains the work we want to execute in this task and a Trigger object (the “When”) that specifies when the task should be triggered.
In our example, our task simply prints a log with the current time.
The Trigger parameter is the “magic” part of the implementation. It calculates the next execution time every time the task finishes:
- Fetching the cron expression from our database using the injected repository
- Creating a new CronTrigger object from the cron expression on the fly, and Spring asks that trigger for the next execution time
This means that if we change the cron expression in our database, the trigger callback will pick up the new value immediately when it calculates the next execution time. This is the main difference from the cronLoader bean approach: the cron expression is re-read from the database whenever Spring calculates the next execution time.
5.2. A Simple REST Controller to Update the Cron Expression in Our Database
For easier testing, let’s create a simple REST controller that allows us to quickly update the database record by HTTP request:
@RestController
public class CronController {
private static final Logger log = LoggerFactory.getLogger(CronController.class);
private final CronConfigRepository repository;
public CronController(CronConfigRepository repository) {
this.repository = repository;
}
@GetMapping("/cron")
public String updateCron() {
CronEntity cronEntity = repository.findById(1L)
.orElseThrow(() -> new RuntimeException("Cron expression not found in database"));
cronEntity.setCronExpression("*/10 * * * * ?");
repository.save(cronEntity);
String msg = "[DB] ⏰ Updated cron expression in DB to: */10 * * * * ?";
log.info(msg);
return msg;
}
}
As the example shows, for simplicity, this endpoint always sets the cron to run every 10 seconds.
Next, let’s verify if the SchedulingConfigurer approach can always reschedule the job according to the database change.
5.3. Starting the Application and Observing the Console Outputs
To make the logs easier to read during the demonstration, we can temporarily comment out the annotation-based scheduling line in AnnotationScheduledJob so that only the dynamic task is running:
@Component
public class AnnotationScheduledJob {
// ...
// @Scheduled(cron = "#{@cronLoader}") <-- comment out
public void run() {
// ...
}
}
Next, let’s launch our Spring Boot application. Once it starts, the scheduled job will also begin. Log outputs similar to these will appear in the console:
... DynamicScheduledConfig : ✅ [10:05:05.001] DynamicScheduledConfig executed - cron re-read from DB per execution
... DynamicScheduledConfig : ✅ [10:05:10.001] DynamicScheduledConfig executed - cron re-read from DB per execution
... DynamicScheduledConfig : ✅ [10:05:15.001] DynamicScheduledConfig executed - cron re-read from DB per execution
... DynamicScheduledConfig : ✅ [10:05:20.000] DynamicScheduledConfig executed - cron re-read from DB per execution
Because the data.sql file still seeds “*/5 * * * * ?”, the dynamic task initially runs about every five seconds.
Next, let’s call the “/cron” endpoint to update the cron expression. For example, we can launch this curl command to send a GET request:
curl http://localhost:8080/cron
Then, in the Spring Boot application console, we can see this log, meaning the cron expression has been changed to “every 10 seconds”:
... CronController : [DB] ⏰ Updated cron expression in DB to: */10 * * * * ?
After that, the dynamic task should start following the new interval. In other words, instead of firing every five seconds, subsequent executions should move to a ten-second rhythm:
... DynamicScheduledConfig : ✅ [10:05:30.000] DynamicScheduledConfig executed - cron re-read from DB per execution
... DynamicScheduledConfig : ✅ [10:05:40.002] DynamicScheduledConfig executed - cron re-read from DB per execution
... DynamicScheduledConfig : ✅ [10:05:50.000] DynamicScheduledConfig executed - cron re-read from DB per execution
... DynamicScheduledConfig : ✅ [10:05:60.000] DynamicScheduledConfig executed - cron re-read from DB per execution
This demonstrates the main benefit of SchedulingConfigurer: we can change the cron expression in the database and let the running application adapt without a restart.
6. Conclusion
In this article, we explored two ways to get a scheduled job’s cron value from a database in Spring Boot.
With the cronLoader bean approach, we keep the implementation simple and continue using @Scheduled, but we only load the cron value once during startup.
With SchedulingConfigurer, we take control of task registration and re-read the cron expression from the database whenever Spring calculates the next execution time. This makes the schedule dynamic and allows runtime updates.
So if we only need a startup-time value, a bean-backed @Scheduled setup is often enough. But if we want truly dynamic scheduling, SchedulingConfigurer is the better choice.
As always, the complete source code for the examples is available over on GitHub.

















