How to schedule the Quartz cron jobs on spring boot app startup?

I am integrating Quartz with Spring boot and postgres. I have created all the required tables for quartz.

Issue : The application starts but the Job is not getting executed as per the cron.

I want that the jobs should be automatically scheduled on start-up and should run as per the cron expression.

But right now the application starts and no trigger is fired.

enter image description here

The QuartzJobFactory class

public class QuartzJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        beanFactory = applicationContext.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {

        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }

}

The ConfigQuartz class

@Configuration
public class ConfigQuartz {

    @Value("${quartz.dataSource.myDS.URL}")
    String orgQuartzDatasourceMydsUrl;

    @Value("${spring.datasource.username}")
    String orgQuartzDatasourceMydsUser;

    @Value("${spring.datasource.password}")
    String orgQuartzDatasourceMydsPassword;

    @Value("${quartz.enabled}")
    Boolean isQuartzEnabled;

    private static List<Trigger> triggers = new ArrayList<>();


    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext) {
        QuartzJobFactory sampleJobFactory = new QuartzJobFactory();
        sampleJobFactory.setApplicationContext(applicationContext);
        return sampleJobFactory;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory)
            throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setOverwriteExistingJobs(true);
        factory.setAutoStartup(isQuartzEnabled);
//         factory.setDataSource(dataSource);
        factory.setJobFactory(jobFactory);
        factory.setQuartzProperties(quartzProperties());
        factory.setTriggers(triggers.toArray(new Trigger[triggers.size()]));

        return factory;
    }

    public Properties setQuartzProperties() {
        Properties properties = new Properties();
        properties.setProperty("org.quartz.dataSource.myDS.URL",orgQuartzDatasourceMydsUrl);
        properties.setProperty("org.quartz.dataSource.myDS.user",orgQuartzDatasourceMydsUser);
        properties.setProperty("org.quartz.dataSource.myDS.password",orgQuartzDatasourceMydsPassword);
        return properties;
    }

    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
        propertiesFactoryBean.setProperties(setQuartzProperties());
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    public static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs,
                                                         String triggerName) {
        SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
        factoryBean.setJobDetail(jobDetail);
        factoryBean.setStartDelay(0L);
        factoryBean.setRepeatInterval(pollFrequencyMs);
        factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
        factoryBean.setName(triggerName);
        // in case of misfire, ignore all missed triggers and continue :
        factoryBean.setMisfireInstruction(
                SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
        return factoryBean;
    }

    // Use this method for creating cron triggers instead of simple triggers:
    public static CronTriggerFactoryBean createCronTrigger(JobDetail jobDetail, String cronExpression,
                                                           String triggerName) throws Exception {
        CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
        factoryBean.setJobDetail(jobDetail);
        factoryBean.setCronExpression(cronExpression);
        factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
        factoryBean.setName(triggerName);
        try {
            factoryBean.afterPropertiesSet();
        } catch (ParseException e) {
            throw new Exception(e.getMessage());
        }
        triggers.add(factoryBean.getObject());
        return factoryBean;
    }

    public static JobDetailFactoryBean createJobDetail(Class jobClass) {
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        factoryBean.setJobClass(jobClass);
        // job has to be durable to be stored in DB:
        factoryBean.setDurability(true);
        return factoryBean;
    }

}

The QuartzJobFactory class

public class QuartzJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        beanFactory = applicationContext.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {

        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }

}

The CronExpression class

public class CronExpression {
    private static final Map<String, String> cronExpressionMap = ImmutableMap
            .<String, String>builder()
            .put(TestJob.CLASS_NAME, "* 0 0 ? * * *")
            .build();

    public static String get(String key) {
        return cronExpressionMap.get(key);
    }
}

The TestJob class

@Component
@DisallowConcurrentExecution
public class TestJob implements Job {

    public static final String CLASS_NAME = "TestJob";

    private final String JOB_BEAN_NAME = CLASS_NAME + AppConstants.QUARTZ_JOB_SUFFIX;

    private final String TRIGGER_BEAN_NAME = CLASS_NAME + AppConstants.QUARTZ_TRIGGER_SUFFIX;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        LocalDateTime start = LocalDateTime.now();
        System.out.println("*************** execute method is running *******");
    }

    @Bean(name = JOB_BEAN_NAME)
    public JobDetailFactoryBean sampleJob() {
        return ConfigQuartz.createJobDetail(this.getClass());
    }

    @Bean(name = TRIGGER_BEAN_NAME)
    public CronTriggerFactoryBean sampleJobTrigger(
            @Qualifier(JOB_BEAN_NAME) JobDetailFactoryBean jobDetail) throws Exception {
        return ConfigQuartz.createCronTrigger(jobDetail.getObject(), CronExpression.get(CLASS_NAME),
                CLASS_NAME);
    }
}

The main class

EnableConfigurationProperties
@EntityScan(basePackages = {"com.example"})
@ComponentScan({"com.example"})
@SpringBootApplication
@EnableScheduling
public class QuartzSchedulerApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuartzSchedulerApplication.class, args);
    }

}

application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/quartz
spring.datasource.username=postgres
spring.datasource.password=mysecretpassword
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect
spring.jpa.show-sql=true
spring.jpa.database=POSTGRESQL
spring.datasource.platform=postgres
spring.datasource.driverClassName=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=none
spring.datasource.hikari.maximum-pool-size=5

spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false

quartz.dataSource.myDS.URL=jdbc:postgresql://localhost:5432/quartz
quartz.enabled=true

The quartz.properties

#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName = PropScheduler
org.quartz.scheduler.instanceId = 10
#org.quartz.scheduler.instanceId = AUTO
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource=myDS

org.quartz.jobStore.isClustered = true

org.quartz.jobStore.clusterCheckinInterval = 5000
org.quartz.scheduler.name=test-schedulers

#============================================================================
# Configure Datasources
#============================================================================

org.quartz.dataSource.myDS.driver=org.postgresql.Driver

org.quartz.dataSource.myDS.maxConnections = 5
org.quartz.dataSource.myDS.validationQuery = select 1

#============================================================================
# Configure trigger history loging , enble if needed more information regaring triggers
#============================================================================
#org.quartz.plugin.triggerHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin
#org.quartz.plugin.triggerHistory.triggerFiredMessage=Trigger [{1}.{0}] fired job [{6}.{5}] scheduled at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}, next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
#org.quartz.plugin.triggerHistory.triggerCompleteMessage=Trigger [{1}.{0}] completed firing job [{6}.{5}] with resulting trigger instruction code: {9}. Next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
#org.quartz.plugin.triggerHistory.triggerMisfiredMessage=Trigger [{1}.{0}] misfired job [{6}.{5}]. Should have fired at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here


Solution 1:

Your TriggerListener class (SimpleTriggerFactoryBean) doesn't seem to be implementing TriggerListener interface of quartz.

You can create custom class which implements TriggerListener and see if that works.

Reference