Jul 9, 2013

Configuring Quartz 2 with Spring in clustered mode

Aligning the stars to configure Quartz 2.1.7 to work with Spring 3.1.3 in a cluster was surprisingly complicated. The main idea is to run jobs to fire only once per cluster, not once per server, while still providing beans from the Spring managed context and using the latest version of Quartz. The documentation consists essentially of a number of blog posts and stackoverflow answers. So here is one final and (hopefully) more comprehensive summary of the process.

For the TL;DR version, just see the full github gist.

In Quartz.properties we’ll want to set useProperties=true so that data persisted to the DB is in String form instead of Serialized Java objects. But unfortunately the Spring 3.1.x CronTriggerFactoryBean sets a jobDetails property as a Java object, so Quartz will complain that the data is not a String. We’ll need to create our own PersistableCronTriggerFactoryBean to get around this issue (similar to this blog post and forum discussion).

Additionally, in our Spring config the SchedulerFactoryBean will need to set both the triggers and the jobDetails objects. We also setup the scheduler to use Spring’s dataSource and transactionManager. And notice that durability=true must be set on each JobDetailFactoryBean.

By default you cannot use Autowired capabilities in the Quartz Jobs, but this can be easily setup with a AutowiringSpringBeanJobFactory.

You’ll also notice that we cannot use MethodInvokingJobDetailFactoryBean because it is not serializable, so we need to create our own Job class that extends QuartzJobBean. If your services are secured by Acegi or Spring Security, you will also need to register an authenticated quartzUser object with the security context.

And finally, we’ll want to test that the trigger’s Cron expression actually fires when we want it to. Here is an example test case that pulls the cronExpression from configuration and tests that it fires correctly on 2 consecutive days:

Hopefully this helps others in configuring an enterprise-ready Quartz + Spring application to run jobs in a clustered server environment.

About the Author

Object Partners profile.

One thought on “Configuring Quartz 2 with Spring in clustered mode

  1. Ted Naleid says:

    Thanks for posting this! it was very helpful in getting quartz+spring+sql server set up on a project I’m on now. The docs on this are all scattered around and incomplete, but you’ve got everything in one place.

  2. Jeff Sheets says:

    Great, I’m glad this was able to help you out!

  3. Ian Lim says:

    Thanks for the posting. It’s very helpful.

  4. SAURAV says:

    Thank you. It was really helpful. It saved hell lot of time to integrate Spring & Quartz. I didn’t understand the point of extending CronTriggerFactoryBean i.e PersistableCronTriggerFactoryBean. I am just wondering, in my case I want to define different job templates for our various category of Jobs. And cronexpression will be created from user interface. Now I want to attach this Job class with the CronExpression and put few of other details in JobMap which I can pull out those information during the execution of respective Job based cronexpression. Currently, I am using oracle database. I have the done same without Spring in SQLServer and Java.

  5. Jeff Sheets says:

    Yeah, the persistable configuration is a bit confusing to say the least. In Quartz config you’ll want to set useProperties=true so that only String properties are serialized to the database. Otherwise Quartz will serialize the Java objects themselves which will cause issues when migrating new version of those objects to production. But because of a ‘bug’ with Spring you can’t use the standard CronTriggerFactoryBean to get the String behavior. (This may have been fixed in the latest Spring releases, I’m not sure)

    Also, since you’re using a UI to define cron schedules, you may want to set the Spring overwriteExistingJobs to false. Otherwise on every deploy the db values will be overwritten by what is in the properties file. In my case I wanted that behavior so I set it to true, but it seems that might cause issues in your scenario.

  6. Chintan says:

    I made test app along the same lines. Its a plain java app. My prime objective was to handle misfires. I have scheduled a cron job to run at every five minutes. To test, I stopped the Java app and started after some time. But no any extra job executions are triggered to handle the misfires. Even DB table shows the new start time of the trigger whenever I execute the java app.

  7. Saurav says:

    Hi, I tried to integrate and create a Crontrigger and attach with a Job class but it’s throwing an exception
    Exception thrown during adding cron expression to jobClass :; nested exception is org.quartz.SchedulerException: Based on configured schedule, the given trigger will never fire.

    The cron expression what I tried is with 0 0/1 11-23 ? * SUN,MON-FRI

  8. Jeff Sheets says:

    It might help to experiment with this quartz cron maker page:
    http://www.cronmaker.com/

  9. Saurav says:

    I tried with this cron expression too
    0 0/10 * * * ?

  10. Saurav says:

    While developing with Java and SQLServer 2008. I followed this cronmaker website and the similar user interface I built to cron expression

  11. Saurav says:

    I mean what might be the problem. As it says the trigger will never fire. What are the possible reasons? Am I feeding wrong cron expression or it can be an issue spring and quartz?

  12. Saurav says:

    I tried it with 0 0/1 * 1/1 * ? * from cronmaker website tool but didn’t work 🙁

  13. Enrique RodrĂ­guez says:

    Very useful stuff! Thanks a lot!

  14. Nikolaos Abatzis says:

    Jeff, great info thanks. I am running into a weird problem. My applicationContext gets initialized twice !!! the second time everything gets hosed, the connection to the DB is already taken and the application does not work at all. What causess this is the “quartzScheduler” bean. Any ideas why?
    Thanks.

  15. Ravi says:

    HI,

    I had faced this issue , Could you please suggest me what is the issue.

    2015-04-29 08:36:13.436::WARN: Nested in org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘optimizationScheduler’ defined in ServletContext resource [/WEB-INF/iim-quartz-config.xml]: Invocation of init method failed; nested exception is org.quartz.JobPersistenceException: Couldn’t store trigger ‘DEFAULT.cronTrigger’ for ‘DEFAULT.sendEmailPersistJob’ job:ORA-01950: no privileges on tablespace ‘USRTS001’
    [See nested exception: java.sql.SQLSyntaxErrorException: ORA-01950: no privileges on tablespace ‘USRTS001’
    ]:
    java.sql.SQLSyntaxErrorException: ORA-01950: no privileges on tablespace ‘USRTS001’

  16. Jeff Sheets says:

    Hi Ravi,
    This looks like a database permissions issue on the USRTS001 schema with the userid that is configured. It will probably require granting some permissions in the Oracle db administration tool.
    — Jeff

  17. Rips says:

    Hi Jeff,
    I am getting following error “Caused by: org.quartz.impl.jdbcjobstore.LockException: Failure obtaining db row lock: Table ‘schema.qrtz_locks’ doesn’t exist”
    The table is already existing in my DB.I am using mysql,psring 4.1.1 and quartz 2.1.1

  18. Harry Yuan says:

    Very help article indeed. one quick question, are there any tables needed to be created in the backend database? If so, what is the script?

  19. Roushan Singh says:

    Hi Jeff,

    I followed exactly the same steps mentioned by you above. When I deploy this code, my Quartz scheduler starts normally but, trigger never gets fired and the QRTZ_TRIGGERS entry in database shows TRIGGER_STATE as WAITING forever. I have used a cron which gets fired every minute and have properly validated that as well. There are no exceptions in any of the logs that I can mention here. I am using quartz scheduler 2.2.1 with spring version 4.2.1.RELEASE. Please suggest.

  20. Atul Singh says:

    I’m trying to run multiple instances pointed to same database suppose we have 4 jobs, 3 of them want to run in Instance-1 and 1 in Instance-2. But when we start both the tool then all the 4 jobs executed by same Instance. how to stop from executing it ? Please help me over this.

  21. Sava says:

    How we can download the full source code? Could you please provide link to download source code ?

  22. Jeff Sheets says:

    Hi Sava,
    Yes here is a link to the full github gist
    https://gist.github.com/jeffsheets/5862630

    — Jeff

  23. Alejandro Rangel says:

    Thanks! It works well, just some little things to fix and complete.

  24. Jacob says:

    Hi, I have set up quartz 2 with Spring 4 and weblogic 12c cluster with oracle 12c database. “isClustered” is set to TRUE. I am using CronTriggerFactoryBean. “useProperties” is not explicitly set. Occassionally, the jobs are being started from both nodes. It is like one job has started and is midway when the job on the other node also gets started.

    Any idea what may be causing this?

  25. Jeff Sheets says:

    Jacob, very interesting situation, but unfortunately I haven’t seen that before. It’s almost like the nodes are starting without realizing they should be working together. The JobStore tables should get updated when the first job gets executed. Perhaps watch that table to see if it gives any clues as to what is happening?

  26. Aakash Patel says:

    Hi, which maximum version of Quartz API can i use for spring 3.2.0 and Java6 in My project?
    Currently i am using Quarts 1.8.6 with Spring 3.2.0 and Java 6. Upto which version i can upgrade quartz?

  27. Jeff Sheets says:

    Hi Aakash, I’m unsure what the max version would be. That might be a better question to post on the quartz github project: https://github.com/quartz-scheduler/quartz

  28. Eduardo says:

    Why have you configured FirstJob with scope Prototype and not Singleton? Is that mandatory?

  29. Jeff Sheets says:

    Hi Eduardo, I believe the reason is that we want each Job to be run as its own instance instead of as singletons. Though it has been a while since I wrote this code, so perhaps a singleton would be fine too

  30. Rose says:

    Hi Jeff- This is Rose. I am not sure if you remember me, I have worked with you previously.:)..Was wonderful reading your blog!

    I am trying to configure a spring batch with spring boot(2.0 with spring 5) and using quartz (2.*)for scheduling. I am trying to set up a oracle job store for clustering and run into this exception when trying to run the spring boot app.
    Caused by: org.quartz.JobPersistenceException: The job (DEFAULT.jobDetailFactoryBean) referenced by the trigger does not exist.

    Have the job detail and trigger beans set up like this

    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean() {
    JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
    jobDetailFactoryBean.setJobClass(ReclaimPkgJobLauncher.class);
    jobDetailFactoryBean.setDurability(true);
    Map map = new HashMap();
    map.put(“jobName”, jobName);
    map.put(“jobLauncher”, jobLauncher);
    map.put(“jobLocator”, jobLocator);

    jobDetailFactoryBean.setJobDataAsMap(map);

    return jobDetailFactoryBean;
    }

    @Bean
    public PersistableCronTriggerFactoryBean cronTriggerFactoryBean() {
    PersistableCronTriggerFactoryBean cronTriggerFactoryBean = new PersistableCronTriggerFactoryBean();
    cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean().getObject());
    // run every 10 seconds
    cronTriggerFactoryBean.setCronExpression(jobProperties.getJobs().get(jobName).getCron());

    return cronTriggerFactoryBean;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
    SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
    schedulerFactoryBean.setTriggers(cronTriggerFactoryBean().getObject());
    schedulerFactoryBean.setQuartzProperties(quartzProperties());
    schedulerFactoryBean.setDataSource(dataSource);

    return schedulerFactoryBean;
    }

    The jobLauncher class has the execute method overriden. Why wouldn’t it find the right instance of the job detail? Any help is appreciated. Thank you.

    1. Rose says:

      The solution to the above problem was removing the job launcher, locator from the job data map. This was causing the persistence errors as we do not want objects to be serialized in the jobDataMap.

Leave a Reply to Saurav Cancel reply

Your email address will not be published.

Related Blog Posts
Natively Compiled Java on Google App Engine
Google App Engine is a platform-as-a-service product that is marketed as a way to get your applications into the cloud without necessarily knowing all of the infrastructure bits and pieces to do so. Google App […]
Building Better Data Visualization Experiences: Part 2 of 2
If you don't have a Ph.D. in data science, the raw data might be difficult to comprehend. This is where data visualization comes in.
Unleashing Feature Flags onto Kafka Consumers
Feature flags are a tool to strategically enable or disable functionality at runtime. They are often used to drive different user experiences but can also be useful in real-time data systems. In this post, we’ll […]
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]