Fixing Spring Boot Quartz Integration: JobFactory and Autowiring Explained

After discovering misconfigurations in a Spring Boot project’s QuartzJobBean implementation, this article examines the root causes, compares legacy and modern Spring solutions—including custom JobFactory, AutowireCapableBeanFactory, and the spring-boot-starter-quartz starter—and provides detailed code examples to correctly integrate Quartz with Spring’s IoC container.

Tech Musings
Tech Musings
Tech Musings
Fixing Spring Boot Quartz Integration: JobFactory and Autowiring Explained

Problem Discovery

While reviewing an older Spring Boot project, the author noticed that the QuartzJobBean implementation ( UnprocessedTaskJob) was annotated with @Component and injected a TaskMapper via @Autowired. This caused a conflict because Quartz creates a new job instance for each trigger, while Spring treats the job class as a singleton bean.

Root Cause

The conflict arises from the fact that Quartz instantiates job classes via reflection, ignoring Spring's IoC container. When the job class is also a Spring singleton, the container tries to inject dependencies that are not available in the Quartz‑created instance, leading to runtime errors.

Legacy Solution (Spring 4.x / Quartz 2.1.4)

To bridge the gap, a custom JobFactory is introduced. The classic approach extends SpringBeanJobFactory and implements ApplicationContextAware so that the created job instance can be autowired.

public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
    private AutowireCapableBeanFactory beanFactory;
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.beanFactory = context.getAutowireCapableBeanFactory();
    }
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

The factory is then registered with the scheduler:

@Bean
public JobFactory jobFactory(ApplicationContext ctx) {
    AutowiringSpringBeanJobFactory factory = new AutowiringSpringBeanJobFactory();
    factory.setApplicationContext(ctx);
    return factory;
}

@Bean
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource ds) {
    SchedulerFactoryBean bean = new SchedulerFactoryBean();
    bean.setJobFactory(jobFactory);
    bean.setDataSource(ds);
    bean.setOverwriteExistingJobs(true);
    bean.setAutoStartup(true);
    bean.setStartupDelay(2);
    return bean;
}

New Solution (Spring Boot 2.x+)

Spring Boot provides the spring-boot-starter-quartz starter, which auto‑configures a SchedulerFactoryBean and a default SpringBeanJobFactory. The newer SchedulerFactoryBean already implements ApplicationContextAware, so explicit factory beans are often unnecessary.

implementation "org.springframework.boot:spring-boot-starter-quartz"

When the starter is on the classpath, all @JobDetail, @Trigger, and @Bean definitions are automatically registered with the scheduler. Autowiring works out‑of‑the‑box as long as the job class is a Spring component.

SpringBeanJobFactory Deep Dive

The SpringBeanJobFactory extends AdaptableJobFactory and implements both ApplicationContextAware and SchedulerContextAware. Its createJobInstance method first creates the job via reflection, then populates it with properties from the scheduler context, job data map, and trigger data map. If an ApplicationContext is present, it uses the AutowireCapableBeanFactory to perform constructor injection, enabling full Spring dependency injection.

protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
    Object job = (applicationContext != null) ?
        applicationContext.getAutowireCapableBeanFactory()
            .createBean(bundle.getJobDetail().getJobClass(), AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false)
        : super.createJobInstance(bundle);
    // Populate properties from scheduler context and job data maps
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
    MutablePropertyValues pvs = new MutablePropertyValues();
    if (schedulerContext != null) pvs.addPropertyValues(schedulerContext);
    pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
    pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
    bw.setPropertyValues(pvs, true);
    return job;
}

AutowireCapableBeanFactory Role

When a bean is not managed by Spring (e.g., a Quartz‑created job), the AutowireCapableBeanFactory can inject dependencies manually. By calling beanFactory.autowireBean(job), the job instance receives all @Autowired fields, allowing it to interact with other Spring‑managed services such as repositories or mappers.

Conclusion

For legacy Spring versions, a custom JobFactory (often extending SpringBeanJobFactory) is required to bridge Quartz and Spring IoC. In modern Spring Boot applications, the starter handles most configuration automatically, but understanding the underlying JobFactory and AutowireCapableBeanFactory mechanisms is valuable for advanced scenarios or when fine‑grained control is needed.

JavaQuartzspring-bootautowiringjobfactory
Tech Musings
Written by

Tech Musings

Capturing thoughts and reflections while coding.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.