DB-Scheduler: A Reliable Scheduler for Java
Scheduling background jobs is a common requirement in Java applications. Applications often need to send notifications, generate reports, clean up old data, synchronize information, or execute business operations at a specific time. For simple applications, an in-memory scheduler such as ScheduledExecutorService may be sufficient. However, it has an important limitation: scheduled tasks exist only inside the running application instance. If the application restarts, scheduled information can be lost. Running multiple application instances also makes coordination more difficult because every instance may try to execute the same job. DB-Scheduler addresses these problems by storing scheduled tasks in a relational database. This makes scheduling persistent and allows multiple application instances to work together in a cluster. Instead of maintaining the complete scheduling state only in application memory, the database provides persistent storage for scheduled tasks.
1. What Is DB-Scheduler?
DB-Scheduler is a lightweight Java library for scheduling persistent background tasks using a relational database. It is designed for applications where scheduled work should survive application restarts and where multiple application instances may run simultaneously. A task can be stored in the database with information such as its identifier, execution time, task data, and recurrence information. DB-Scheduler periodically checks for tasks that are ready to execute and coordinates their execution.
The main benefits of DB-Scheduler include:
- Persistence: Scheduled tasks are stored in a relational database and can survive application restarts.
- Cluster support: Multiple application instances can share the same task store.
- One-time execution: Tasks can execute once and then be completed.
- Recurring execution: Tasks can run repeatedly using predefined or dynamically calculated schedules.
- Database-backed coordination: The database is used to coordinate task execution between application instances.
- Simple integration: DB-Scheduler can be integrated into Java applications that already use a relational database.
1.1 How It Works
At a high level, DB-Scheduler follows a simple workflow:
- An application defines a scheduled task.
- A task instance is scheduled and persisted in the database.
- The scheduler checks the database for tasks that are ready to run.
- An available application instance picks up the task for execution.
- The task is executed.
- The scheduler completes the task or calculates the next execution time for a recurring task.
Because the scheduled task information is persisted in the database, restarting one application instance does not remove the task. In a clustered application, other instances can continue processing scheduled work using the same database.
2. Code Example
The following example demonstrates three common scheduling scenarios:
- A one-time task.
- A simple recurring task.
- A dynamically recurring task where the next execution time is calculated dynamically.
2.1 Prerequisites
DB-Scheduler 16.x requires Java 17 or later. The application also needs access to a supported relational database because DB-Scheduler persists scheduled task information in the database.
2.2 Maven Dependency
Add DB-Scheduler to the Maven project. Use the current stable version appropriate for the application:
<dependency> <groupId>com.github.kagkarlsson</groupId> <artifactId>db-scheduler</artifactId> <version>CURRENT_VERSION</version> </dependency>
2.3 Database Setup
DB-Scheduler stores scheduled task information in a relational database. Before starting the scheduler, the database must contain the scheduled_tasks table required by the DB-Scheduler version being used. The project provides database-specific schema definitions for supported databases. For production applications, use the schema provided by the corresponding DB-Scheduler version rather than creating a simplified table manually. This ensures that all required columns, indexes, and database-specific definitions are available. For example, with PostgreSQL, the DB-Scheduler project provides the appropriate schema for creating the required task table. The application should execute that schema as part of its database initialization or migration process before starting the scheduler.
2.4 Complete Java Example
// DbSchedulerExample.java
import com.github.kagkarlsson.scheduler.Scheduler;
import com.github.kagkarlsson.scheduler.task.Task;
import com.github.kagkarlsson.scheduler.task.helper.Tasks;
import com.github.kagkarlsson.scheduler.task.schedule.Schedules;
import javax.sql.DataSource;
import java.time.Duration;
import java.time.Instant;
public class DbSchedulerExample {
public static void main(String[] args) {
DataSource dataSource = createDataSource();
// 1. One-time task - The task executes once and is then completed.
Task < String > oneTimeTask =
Tasks.oneTime("send-welcome-email", String.class)
.execute((instance, taskData) -> {
System.out.println("One-time task executed for: " + taskData);
});
// 2. Simple recurring task - Executes every 30 seconds.
Task < Void > recurringTask = Tasks
.recurring("health-check", Schedules.fixedDelay(Duration.ofSeconds(30)))
.execute((instance, taskData) -> {
System.out.println("Recurring health check executed at " + Instant.now());
});
// 3. Dynamic recurring task -
// The next execution time is calculated dynamically.
Task < String > dynamicTask = Tasks.recurring("dynamic-job",
(executionTime, taskData) -> {
// Schedule the next execution 60 seconds later.
return executionTime.plusSeconds(60);
}
).execute((instance, taskData) -> {
System.out.println("Dynamic task executed: " + taskData);
});
// Build and start the scheduler.
Scheduler scheduler =
Scheduler.create(dataSource).startTasks(oneTimeTask, recurringTask, dynamicTask).threads(2).build();
scheduler.start();
System.out.println("DB-Scheduler started.");
// Schedule the one-time task.
scheduler.schedule(oneTimeTask.instance("Welcome email for user-101"));
// Schedule the recurring tasks.
scheduler.schedule(recurringTask.instance());
scheduler.schedule(dynamicTask.instance("Process dynamic job"));
System.out.println("Tasks scheduled.");
}
private static DataSource createDataSource() {
// Configure the DataSource according to the application's database configuration.
return createPostgresDataSource();
}
private static DataSource createPostgresDataSource() {
// Database configuration omitted for brevity.
throw new UnsupportedOperationException("Configure your PostgreSQL DataSource here");
}
}
2.4.1 Code Explanation
The example starts by creating a DataSource, which provides the database connection used by DB-Scheduler. The first task is a one-time task created using Tasks.oneTime(). The task is given the name send-welcome-email and uses String as its task-data type. The execute() method defines the operation that should be performed when the task runs. In this example, the task simply prints the supplied user information to the console. The second task is a recurring task created using Tasks.recurring() and Schedules.fixedDelay(). The Duration.ofSeconds(30) configuration specifies a 30-second fixed delay between executions. The task uses Void because it does not require any task-specific data. Each time it executes, it prints the current timestamp using Instant.now(). The third task demonstrates a dynamic recurring schedule. Instead of using a predefined schedule such as fixedDelay(), the scheduling function receives the execution time and task data and returns the next execution time. In this example, executionTime.plusSeconds(60) schedules the next execution 60 seconds after the current execution time. This approach is useful when the next execution needs to be determined dynamically based on application-specific rules. After defining the tasks, the application creates the scheduler using Scheduler.create(dataSource). The supplied DataSource allows DB-Scheduler to use the relational database for its persistent task store. The startTasks() method registers the task definitions with the scheduler, while threads(2) configures two worker threads that can execute scheduled tasks. The build() method creates the scheduler, and scheduler.start() starts its background scheduling process. The application then creates task instances using the instance() method and schedules them using scheduler.schedule(). The one-time task receives "Welcome email for user-101" as its task data. The recurring health-check task does not require any data, so it uses recurringTask.instance(). The dynamic task receives "Process dynamic job" as its task data. DB-Scheduler persists these scheduled task instances in the database and executes them when they become due. The createDataSource() method delegates to createPostgresDataSource(). The latter is intentionally left as a placeholder because database connection configuration depends on the application environment. In a real application, it would create and configure a PostgreSQL DataSource with the database URL, credentials, connection pool, and other required settings.
2.4.2 Code Output
When the application starts, DB-Scheduler initializes the scheduler and begins processing the scheduled task instances. The one-time task executes once, while the recurring tasks continue according to their respective schedules. A console output is shown below:
DB-Scheduler started. Tasks scheduled. One-time task executed for: Welcome email for user-101 Recurring health check executed at 2026-09-05T08:30:30Z Dynamic task executed: Process dynamic job Recurring health check executed at 2026-09-05T08:31:00Z Dynamic task executed: Process dynamic job Recurring health check executed at 2026-09-05T08:31:30Z
The exact timestamps and ordering may vary depending on the application startup time, database response time, and scheduler worker threads. The important point is that the one-time task executes once, the simple recurring task continues with its configured 30-second delay, and the dynamic recurring task calculates its next execution 60 seconds after the current execution time.
2.5 Using DB-Scheduler with Spring Boot
DB-Scheduler can also be integrated into Spring Boot applications. Instead of creating the DataSource and Scheduler manually, Spring Boot can manage the database connection and application lifecycle. This makes DB-Scheduler a convenient option for applications that already use Spring Boot and a relational database. The following example shows how to configure DB-Scheduler as a Spring-managed bean:
import com.github.kagkarlsson.scheduler.Scheduler;
import com.github.kagkarlsson.scheduler.task.Task;
import com.github.kagkarlsson.scheduler.task.helper.Tasks;
import com.github.kagkarlsson.scheduler.task.schedule.Schedules;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.time.Duration;
@Configuration
public class SchedulerConfig {
@Bean
public Task<Void> cleanupTask() {
return Tasks.recurring(
"cleanup-task",
Schedules.fixedDelay(Duration.ofHours(1))
).execute((instance, taskData) -> {
System.out.println("Cleanup task executed.");
});
}
@Bean
public Scheduler scheduler(
DataSource dataSource,
Task<Void> cleanupTask) {
Scheduler scheduler = Scheduler.create(
dataSource,
cleanupTask
).threads(2).build();
scheduler.start();
return scheduler;
}
}
In this example, Spring Boot provides the DataSource, while the cleanupTask() method defines a recurring DB-Scheduler task. The Scheduler is registered as a Spring bean, allowing Spring to manage it as part of the application’s lifecycle. The task runs with a fixed one-hour delay and uses the same relational database configured by the Spring Boot application. The required database connection can be configured using the standard Spring Boot properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/myapp spring.datasource.username=postgres spring.datasource.password=secret
Once the application starts, Spring Boot creates the DataSource, initializes the scheduler, and starts processing scheduled tasks. The DB-Scheduler task table must still be created using the schema appropriate for the DB-Scheduler version and database before the scheduler starts. This approach keeps the DB-Scheduler configuration close to the application’s Spring configuration while allowing Spring Boot to manage database connectivity and application lifecycle. For larger applications, task definitions can also be separated into individual configuration or service classes to keep the scheduler configuration easier to maintain.
3. Conclusion
DB-Scheduler provides a practical approach to background task scheduling for Java applications that need persistent and cluster-friendly execution. Instead of keeping scheduling state only in application memory, it uses a relational database to persist scheduled tasks and coordinate their execution. Its API supports common scheduling patterns, including one-time tasks, fixed recurring tasks, and dynamically calculated recurring tasks. This makes it useful for operations such as notifications, data synchronization, cleanup jobs, report generation, polling, and periodic application maintenance. For applications that already use a relational database, DB-Scheduler can be a simple alternative to introducing a separate distributed scheduling infrastructure when the application’s scheduling requirements are relatively straightforward. It provides persistence and multi-instance coordination while keeping the scheduling API relatively simple for Java developers.

