Core Java

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.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button