diff --git a/spring-boot-modules/pom.xml b/spring-boot-modules/pom.xml
index 527f7dcad8..fa70a9f058 100644
--- a/spring-boot-modules/pom.xml
+++ b/spring-boot-modules/pom.xml
@@ -46,6 +46,7 @@
spring-boot-jasypt
spring-boot-keycloak
spring-boot-libraries
+ spring-boot-libraries-2
spring-boot-logging-log4j2
spring-boot-kotlin
spring-boot-mvc
diff --git a/spring-boot-modules/spring-boot-libraries-2/README.md b/spring-boot-modules/spring-boot-libraries-2/README.md
new file mode 100644
index 0000000000..b0840798e3
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-2/README.md
@@ -0,0 +1,7 @@
+## Spring Boot Libraries
+
+This module contains articles about various Spring Boot libraries
+
+### Relevant Articles:
+
+- Running background jobs in Spring with JobRunr
diff --git a/spring-boot-modules/spring-boot-libraries-2/pom.xml b/spring-boot-modules/spring-boot-libraries-2/pom.xml
new file mode 100644
index 0000000000..2633c8fad3
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-2/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+ spring-boot-modules
+ com.baeldung.spring-boot-modules
+ 1.0.0-SNAPSHOT
+
+ 4.0.0
+
+ spring-boot-libraries-2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.jobrunr
+ jobrunr-spring-boot-starter
+ ${jobrunr.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.awaitility
+ awaitility
+ ${awaitility.version}
+ test
+
+
+
+
+ 1.0.0
+ 4.0.3
+
+
+
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/JobRunrSpringBootApp.java b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/JobRunrSpringBootApp.java
new file mode 100644
index 0000000000..d72e9464d9
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/JobRunrSpringBootApp.java
@@ -0,0 +1,37 @@
+package com.baeldung.jobrunr;
+
+import com.baeldung.jobrunr.service.SampleJobService;
+import org.jobrunr.jobs.mappers.JobMapper;
+import org.jobrunr.scheduling.JobScheduler;
+import org.jobrunr.scheduling.cron.Cron;
+import org.jobrunr.storage.InMemoryStorageProvider;
+import org.jobrunr.storage.StorageProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+import javax.annotation.PostConstruct;
+
+@SpringBootApplication
+public class JobRunrSpringBootApp {
+
+ @Autowired
+ private JobScheduler jobScheduler;
+
+ public static void main(String[] args) {
+ SpringApplication.run(JobRunrSpringBootApp.class, args);
+ }
+
+ @Bean
+ public StorageProvider storageProvider(JobMapper jobMapper) {
+ InMemoryStorageProvider storageProvider = new InMemoryStorageProvider();
+ storageProvider.setJobMapper(jobMapper);
+ return storageProvider;
+ }
+
+ @PostConstruct
+ public void scheduleRecurrently() {
+ jobScheduler.scheduleRecurrently(x -> x.executeSampleJob("a recurring job"), Cron.every5minutes());
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/controller/JobRunrController.java b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/controller/JobRunrController.java
new file mode 100644
index 0000000000..af5f0b1196
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/controller/JobRunrController.java
@@ -0,0 +1,43 @@
+package com.baeldung.jobrunr.controller;
+
+import com.baeldung.jobrunr.service.SampleJobService;
+import org.jobrunr.scheduling.JobScheduler;
+import org.springframework.boot.context.properties.bind.DefaultValue;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+
+@RestController
+@RequestMapping("/jobrunr")
+public class JobRunrController {
+
+ private JobScheduler jobScheduler;
+ private SampleJobService sampleJobService;
+
+ private JobRunrController(JobScheduler jobScheduler, SampleJobService sampleJobService) {
+ this.jobScheduler = jobScheduler;
+ this.sampleJobService = sampleJobService;
+ }
+
+ @GetMapping(value = "/enqueue/{input}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity enqueue(@PathVariable("input") @DefaultValue("default-input") String input) {
+ jobScheduler.enqueue(() -> sampleJobService.executeSampleJob(input));
+ return okResponse("job enqueued successfully");
+ }
+
+ @GetMapping(value = "/schedule/{input}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity schedule(
+ @PathVariable("input") @DefaultValue("default-input") String input,
+ @RequestParam("scheduleAt") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime scheduleAt) {
+ jobScheduler.schedule(() -> sampleJobService.executeSampleJob(input), scheduleAt);
+ return okResponse("job scheduled successfully");
+ }
+
+ private ResponseEntity okResponse(String feedback) {
+ return new ResponseEntity<>(feedback, HttpStatus.OK);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/service/SampleJobService.java b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/service/SampleJobService.java
new file mode 100644
index 0000000000..dff4309374
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-2/src/main/java/com/baeldung/jobrunr/service/SampleJobService.java
@@ -0,0 +1,36 @@
+package com.baeldung.jobrunr.service;
+
+import org.jobrunr.jobs.annotations.Job;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Service
+public class SampleJobService {
+
+ public static final long EXECUTION_TIME = 5000L;
+
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ private AtomicInteger count = new AtomicInteger();
+
+ @Job(name = "The sample job with variable %0", retries = 2)
+ public void executeSampleJob(String variable) {
+
+ logger.info("The sample job has begun. The variable you passed is {}", variable);
+ try {
+ Thread.sleep(EXECUTION_TIME);
+ } catch (InterruptedException e) {
+ logger.error("Error while executing sample job", e);
+ } finally {
+ count.incrementAndGet();
+ logger.info("Sample job has finished...");
+ }
+ }
+
+ public int getNumberOfInvocations() {
+ return count.get();
+ }
+}
diff --git a/spring-boot-modules/spring-boot-libraries-2/src/main/resources/application.properties b/spring-boot-modules/spring-boot-libraries-2/src/main/resources/application.properties
new file mode 100644
index 0000000000..69f5a7356e
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-2/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+org.jobrunr.background_job_server=true
+org.jobrunr.dashboard=true
diff --git a/spring-boot-modules/spring-boot-libraries-2/src/test/java/com/baeldung/jobrunr/JobRunrLiveTest.java b/spring-boot-modules/spring-boot-libraries-2/src/test/java/com/baeldung/jobrunr/JobRunrLiveTest.java
new file mode 100644
index 0000000000..83222e7726
--- /dev/null
+++ b/spring-boot-modules/spring-boot-libraries-2/src/test/java/com/baeldung/jobrunr/JobRunrLiveTest.java
@@ -0,0 +1,46 @@
+package com.baeldung.jobrunr;
+
+import org.awaitility.Awaitility;
+import org.jobrunr.jobs.states.StateName;
+import org.jobrunr.storage.StorageProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.Assert.assertEquals;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = DEFINED_PORT, classes = JobRunrSpringBootApp.class)
+public class JobRunrLiveTest {
+
+ @Autowired
+ TestRestTemplate restTemplate;
+
+ @Autowired
+ StorageProvider storageProvider;
+
+ @Test
+ public void givenEndpoint_whenJobEnqueued_thenJobIsProcessedWithin30Seconds() {
+ String response = enqueueJobViaRest("some-input");
+ assertEquals("job enqueued successfully", response);
+
+ await().atMost(30, TimeUnit.SECONDS).until(() -> storageProvider.countJobs(StateName.SUCCEEDED) == 1);
+ }
+
+ private String enqueueJobViaRest(String input) {
+ try {
+ return restTemplate.getForObject(new URI("http://localhost:8080/jobrunr/enqueue/" + input), String.class);
+ } catch (Exception ignored) {
+ ignored.printStackTrace();
+ }
+ return null;
+ }
+}