diff --git a/spring-quartz/pom.xml b/spring-quartz/pom.xml
index 171f7ffd1064..fefbb2643ad9 100644
--- a/spring-quartz/pom.xml
+++ b/spring-quartz/pom.xml
@@ -34,6 +34,11 @@
org.springframework.boot
spring-boot-starter-jdbc
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
org.springframework
@@ -64,6 +69,15 @@
true
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ org.baeldung.springquartz.SpringQuartzApp
+
+
+
diff --git a/spring-quartz/src/main/java/org/baeldung/recovery/SpringQuartzRecoveryApp.java b/spring-quartz/src/main/java/org/baeldung/recovery/SpringQuartzRecoveryApp.java
new file mode 100644
index 000000000000..a7f734b74763
--- /dev/null
+++ b/spring-quartz/src/main/java/org/baeldung/recovery/SpringQuartzRecoveryApp.java
@@ -0,0 +1,18 @@
+package org.baeldung.recovery;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@ComponentScan
+@EnableScheduling
+@SpringBootApplication
+public class SpringQuartzRecoveryApp {
+
+ public static void main(String[] args) {
+ SpringApplication app = new SpringApplication(SpringQuartzRecoveryApp.class);
+ app.setAdditionalProfiles("recovery");
+ app.run(args);
+ }
+}
diff --git a/spring-quartz/src/main/java/org/baeldung/recovery/config/QuartzConfig.java b/spring-quartz/src/main/java/org/baeldung/recovery/config/QuartzConfig.java
new file mode 100644
index 000000000000..0bdc0d0e6b26
--- /dev/null
+++ b/spring-quartz/src/main/java/org/baeldung/recovery/config/QuartzConfig.java
@@ -0,0 +1,31 @@
+package org.baeldung.recovery.config;
+
+import org.quartz.CronScheduleBuilder;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class QuartzConfig {
+
+ @Bean
+ public JobDetail sampleJobDetail() {
+ return JobBuilder.newJob(SampleJob.class)
+ .withIdentity("sampleJob", "group1")
+ .storeDurably()
+ .requestRecovery(true)
+ .build();
+ }
+
+ @Bean
+ public Trigger sampleTrigger(JobDetail sampleJobDetail) {
+ return TriggerBuilder.newTrigger()
+ .forJob(sampleJobDetail)
+ .withIdentity("sampleTrigger", "group1")
+ .withSchedule(CronScheduleBuilder.cronSchedule("0/30 * * * * ?")) // every 30s
+ .build();
+ }
+}
diff --git a/spring-quartz/src/main/java/org/baeldung/recovery/config/SampleJob.java b/spring-quartz/src/main/java/org/baeldung/recovery/config/SampleJob.java
new file mode 100644
index 000000000000..0c24b1a3edeb
--- /dev/null
+++ b/spring-quartz/src/main/java/org/baeldung/recovery/config/SampleJob.java
@@ -0,0 +1,11 @@
+package org.baeldung.recovery.config;
+
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+
+public class SampleJob implements Job {
+ @Override
+ public void execute(JobExecutionContext context) {
+ System.out.println("Executing SampleJob at " + System.currentTimeMillis());
+ }
+}
diff --git a/spring-quartz/src/main/java/org/baeldung/recovery/custom/ApplicationJob.java b/spring-quartz/src/main/java/org/baeldung/recovery/custom/ApplicationJob.java
new file mode 100644
index 000000000000..71c1b8ca6c40
--- /dev/null
+++ b/spring-quartz/src/main/java/org/baeldung/recovery/custom/ApplicationJob.java
@@ -0,0 +1,49 @@
+package org.baeldung.recovery.custom;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+@Entity
+public class ApplicationJob {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+ private boolean enabled;
+ private Boolean completed;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public Boolean getCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(Boolean completed) {
+ this.completed = completed;
+ }
+}
diff --git a/spring-quartz/src/main/java/org/baeldung/recovery/custom/ApplicationJobRepository.java b/spring-quartz/src/main/java/org/baeldung/recovery/custom/ApplicationJobRepository.java
new file mode 100644
index 000000000000..4f717152cadb
--- /dev/null
+++ b/spring-quartz/src/main/java/org/baeldung/recovery/custom/ApplicationJobRepository.java
@@ -0,0 +1,9 @@
+package org.baeldung.recovery.custom;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ApplicationJobRepository extends JpaRepository {
+
+}
diff --git a/spring-quartz/src/main/java/org/baeldung/recovery/custom/DataSeeder.java b/spring-quartz/src/main/java/org/baeldung/recovery/custom/DataSeeder.java
new file mode 100644
index 000000000000..d3d137d5a43d
--- /dev/null
+++ b/spring-quartz/src/main/java/org/baeldung/recovery/custom/DataSeeder.java
@@ -0,0 +1,26 @@
+package org.baeldung.recovery.custom;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DataSeeder implements CommandLineRunner {
+
+ private final ApplicationJobRepository repository;
+
+ public DataSeeder(ApplicationJobRepository repository) {
+ this.repository = repository;
+ }
+
+ @Override
+ public void run(String... args) {
+ if (repository.count() == 0) {
+ ApplicationJob job = new ApplicationJob();
+ job.setName("simpleJob");
+ job.setEnabled(true);
+ job.setCompleted(false);
+
+ repository.save(job);
+ }
+ }
+}
diff --git a/spring-quartz/src/main/java/org/baeldung/recovery/custom/JobInitializer.java b/spring-quartz/src/main/java/org/baeldung/recovery/custom/JobInitializer.java
new file mode 100644
index 000000000000..6948cd70e321
--- /dev/null
+++ b/spring-quartz/src/main/java/org/baeldung/recovery/custom/JobInitializer.java
@@ -0,0 +1,49 @@
+package org.baeldung.recovery.custom;
+
+import org.baeldung.recovery.config.SampleJob;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SimpleScheduleBuilder;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JobInitializer implements ApplicationListener {
+
+ @Autowired
+ private ApplicationJobRepository jobRepository;
+
+ @Autowired
+ private Scheduler scheduler;
+
+ @Override
+ public void onApplicationEvent(ContextRefreshedEvent event) {
+ for (ApplicationJob job : jobRepository.findAll()) {
+ if (job.isEnabled() && (job.getCompleted() == null || !job.getCompleted())) {
+ JobDetail detail = JobBuilder.newJob(SampleJob.class)
+ .withIdentity(job.getName(), "appJobs")
+ .storeDurably()
+ .build();
+
+ Trigger trigger = TriggerBuilder.newTrigger()
+ .forJob(detail)
+ .withSchedule(SimpleScheduleBuilder.simpleSchedule()
+ .withIntervalInSeconds(30)
+ .repeatForever())
+ .build();
+
+ try {
+ scheduler.scheduleJob(detail, trigger);
+ } catch (SchedulerException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+}
diff --git a/spring-quartz/src/main/resources/application-recovery.properties b/spring-quartz/src/main/resources/application-recovery.properties
new file mode 100644
index 000000000000..22a5de2d94f6
--- /dev/null
+++ b/spring-quartz/src/main/resources/application-recovery.properties
@@ -0,0 +1,12 @@
+spring.quartz.job-store-type=jdbc
+# Always create the Quartz database on startup
+spring.quartz.jdbc.initialize-schema=always
+
+spring.datasource.jdbc-url=jdbc:h2:~/quartz-db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=
+
+spring.jpa.hibernate.ddl-auto=create
+
+spring.h2.console.enabled=true
\ No newline at end of file
diff --git a/spring-quartz/src/test/java/org/baeldung/recovery/SpringQuartzRecoveryAppUnitTest.java b/spring-quartz/src/test/java/org/baeldung/recovery/SpringQuartzRecoveryAppUnitTest.java
new file mode 100644
index 000000000000..1dc4d8ada74c
--- /dev/null
+++ b/spring-quartz/src/test/java/org/baeldung/recovery/SpringQuartzRecoveryAppUnitTest.java
@@ -0,0 +1,54 @@
+package org.baeldung.recovery;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ActiveProfiles;
+
+@ActiveProfiles("recovery")
+@SpringBootTest(classes = SpringQuartzRecoveryApp.class)
+class SpringQuartzRecoveryAppUnitTest {
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ private Scheduler scheduler;
+
+ @Test
+ void givenSampleJob_whenSchedulerRestart_thenSampleJobIsReloaded() throws Exception {
+ // Given
+ JobKey jobKey = new JobKey("sampleJob", "group1");
+ TriggerKey triggerKey = new TriggerKey("sampleTrigger", "group1");
+
+ JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+ assertNotNull(jobDetail, "SampleJob exists in running scheduler");
+
+ Trigger trigger = scheduler.getTrigger(triggerKey);
+ assertNotNull(trigger, "SampleTrigger exists in running scheduler");
+
+ // When
+ scheduler.standby();
+ Scheduler restartedScheduler = applicationContext.getBean(Scheduler.class);
+ restartedScheduler.start();
+
+ // Then
+ assertTrue(restartedScheduler.isStarted(), "Scheduler should be running after restart");
+
+ JobDetail reloadedJob = restartedScheduler.getJobDetail(jobKey);
+ assertNotNull(reloadedJob, "SampleJob should be reloaded from DB after restart");
+
+ Trigger reloadedTrigger = restartedScheduler.getTrigger(triggerKey);
+ assertNotNull(reloadedTrigger, "SampleTrigger should be reloaded from DB after restart");
+ }
+
+}