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.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*