Background Job Scheduling in Spring Boot
Learn how to schedule fire-and-forget, delayed, and recurring background jobs in a Spring Boot application using the JobRunr Spring Boot Starter. A persistent, distributed alternative to @Scheduled.
On this page
Spring Boot’s @Scheduled runs tasks in-process with no persistence, no retries, and, in a cluster, every node fires the same task at the same time. ShedLock is a common add-on for that last problem: it adds a distributed lock so only one node runs each task, but it stops there. For full background job processing with persistence, retries, fire-and-forget, and delayed execution, teams reach for Quartz Scheduler, which works but demands significant boilerplate, its own schema, and offers no built-in monitoring.
The JobRunr Spring Boot Starter drops into any Spring Boot application with a single dependency to give you:
- Persistent jobs that survive restarts and crashes
- Automatic retries with exponential back-off on failure
- Cluster-safe execution so only one node runs each job, no matter how many nodes you run
- Built-in dashboard to monitor job progress and history
This guide builds the same newsletter example as the Java quickstart, this time using the Spring Boot Starter.
TipPrefer watching? Check out the full tutorial: How to Run Background Jobs in Spring Boot 4 with JobRunr on YouTube. The complete example project for this guide is available at github.com/jobrunr/jobrunr-examples/spring-app.
Step 1: Add the JobRunr Spring Boot Starter
If you are starting from scratch, generate a project at start.spring.io with the Spring Web dependency selected. The web starter pulls in Jackson, JobRunr will automatically pick it up for its JSON serialization needs.
Add the JobRunr starter to your project:
<dependency>
<groupId>org.jobrunr</groupId>
<artifactId>jobrunr-spring-boot-4-starter</artifactId>
<version>8.6.1</version>
</dependency>
implementation 'org.jobrunr:jobrunr-spring-boot-4-starter:8.6.1'
NoteFor Spring Boot 3, replace
jobrunr-spring-boot-4-starterwithjobrunr-spring-boot-3-starter. Thejobrunr-spring-boot-4-starterartifact requires JobRunr 8.3.0 or higher.
ProUsing JobRunr Pro? Replace
jobrunr-spring-boot-4-starterwithjobrunr-pro-spring-boot-4-starterand add the private Maven repository. See Pro Installation for the full setup.
Step 2: Configure JobRunr
Add three lines to application.properties:
jobrunr.background-job-server.enabled=true
jobrunr.dashboard.enabled=true
jobrunr.database.type=mem
The first two are false by default so your web application does not accidentally start processing jobs. The third tells JobRunr to use an in-memory store so no database setup is required for this example.
Important
jobrunr.database.type=memdoes not survive restarts. Before going to production, remove it and configure a real database. JobRunr will pick up the existing SpringDataSourceautomatically. See Storage.
TipMost aspects of JobRunr are configurable via
application.properties: worker count, retry policy, poll interval, dashboard port, and more. See the Spring Boot Starter configuration reference for all available properties.
Step 3: Build the application
We’ll cover three job patterns:
- Fire-and-forget job: send a confirmation email without making the subscriber wait
- Delayed job: follow up a few days after confirmation with a welcome email
- Recurring job: send the weekly digest on a fixed schedule
Create the EmailService
The EmailService holds the actual email logic. In a real application these methods would call a provider like SendGrid or Mailchimp. Here they print to the console so the example runs without external dependencies.
import org.jobrunr.jobs.annotations.Job;
import org.jobrunr.jobs.annotations.Recurring;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
@Recurring(id = "weekly-digest", cron = "0 0 * * MON")
@Job(name = "Weekly Digest")
public void sendWeeklyDigest() {
System.out.println("Weekly digest sent to all subscribers");
}
public void sendConfirmation(String email) {
System.out.println("Confirmation email sent to " + email);
}
public void sendWelcome(String email) {
System.out.println("Welcome email sent to " + email);
}
}
- JobRunr scans Spring beans for
@Recurringon startup and registers them automatically, no explicit registration code needed. ThesendWeeklyDigestis triggered every Monday.@Recurringreplaces Spring’s@Scheduledfor periodic tasks, JobRunr supportscronandinterval(which is close tofixedRateStringof@Scheduled).
TipYou can also create recurring jobs programmatically with the
scheduleRecurrentlymethod fromJobScheduler.
NoteJobRunr only requires a Java 8 lambda, e.g.,
() -> emailService.sendConfirmation("test@example.com").EmailServiceis a regular Spring service with regular methods; there is noJobinterface to implement or special contract to satisfy.
Create the SubscriptionController
Let’s implement the two remaining job patterns. We’re going to use the JobScheduler bean, which is provided automatically by the starter, to create the jobs.
import org.jobrunr.scheduling.JobScheduler;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@RestController
public class SubscriptionController {
private final JobScheduler jobScheduler;
private final EmailService emailService;
public SubscriptionController(JobScheduler jobScheduler, EmailService emailService) {
this.jobScheduler = jobScheduler;
this.emailService = emailService;
}
@PostMapping("/subscribe")
public ResponseEntity<String> subscribe(@RequestParam String email) {
var jobId = jobScheduler.enqueue(() -> emailService.sendConfirmation(email));
return ResponseEntity.accepted().body("Confirmation email queued with job id " + jobId);
}
@PostMapping("/confirm")
public ResponseEntity<String> confirm(@RequestParam String email) {
var jobId = jobScheduler.schedule(Instant.now().plus(3, ChronoUnit.DAYS),
() -> emailService.sendWelcome(email));
return ResponseEntity.accepted().body("Welcome email scheduled with job id " + jobId);
}
}
Both endpoints return 202 Accepted immediately, the caller never waits for email delivery. If the mail provider is temporarily down, the job is automatically retried. By using a database, you also get crash resilience: if the server goes down mid-job, JobRunr picks it back up on restart.
- Enqueue a fire-and-forget job: a worker picks it up as soon as one is free.
- Schedule a delayed job: stored and executed automatically 3 days from now.
NoteWhen a job lambda references a Spring bean, like
emailService.sendConfirmation(email), JobRunr serializes the method call, not the bean itself. At execution time, JobRunr asks the Spring IoC container for an instance ofEmailService. This means your job methods benefit from the full DI container, including injected repositories, transaction management, and other beans.
Step 4: Try it out
The fastest way to get running is to clone the example project directly:
git clone https://github.com/jobrunr/jobrunr-examples.git
cd jobrunr-examples/spring-app
./gradlew bootRun
Or if you are building from scratch, start the application:
./mvnw spring-boot:run
./gradlew bootRun
Then hit the endpoints:
# Subscribe: sends a confirmation email in the background
curl -X POST "http://localhost:8080/subscribe?email=test@example.com"
# Confirm: schedules a welcome email for 3 days from now
curl -X POST "http://localhost:8080/confirm?email=test@example.com"
Open the dashboard at http://localhost:8000/dashboard:
- The Dashboard tab shows statistics about your job processing system.
- The Jobs tab shows the various jobs in queue or processed by JobRunr.
- The Recurring Jobs tab shows the weekly digest job registered via
@Recurring. - The Servers tab shows the servers executing your jobs.
Next steps
- Use a real database: H2 is fine for local development but data is lost on restart. Before going to production, configure a persistent database. JobRunr picks up any
DataSourcebean automatically — see Storage for all supported databases. - Configure the starter: Worker count, retry policy, poll interval, and dashboard credentials are all configurable via
application.properties. See the full Spring Boot Starter reference. - Explore health and metrics: The starter registers Spring Boot Actuator health indicators automatically. Micrometer metrics are also available to plug into your observability platform of choice.
- JobRunr Pro Scale with JobRunr Pro: Unlock batches, job chaining, priority queues, rate limiting, transaction-aware job creation, and an advanced dashboard. See JobRunr Pro.
