eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (cat=Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

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.

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

eBook Jackson – NPI EA – 3 (cat = Jackson)