Say goodbye to TimerTask for scheduling – use Quartz instead.

In most of the web applications there is always a requirement to schedule task which can be repeated on certain interval. For one of our projects, it was required to call a method every fifteen minutes to monitor a table. Initial implementation was done using TimerTask and was a success. Problems arose when we wanted to configure TimerTask in a clustered environment.

A TimerTask once started will create a new Thread of execution. For a clustered environment it would mean configuring different sleep values for all clusters and also the need to test them at least once. Also our implementation was becoming hard to understand for support team who would be responsible for maintaining our application.

Welcome Quartz – From Quartz Web page http://www.opensymphony.com/quartz/

Quartz is a full-featured, open source job scheduling system that can be integrated with, or used along side virtually any J2EE or J2SE application – from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components or EJBs.

Example for integrating Quartz in a J2EE Application

1. Download Quartz latest release as a zip format from http://www.opensymphony.com/quartz/download.action. From the zip file use quartz-all-<version>.jar which contains almost all the files which are required for Quartz to work.

2. Download Java Transaction API’s (JTA) from http://java.sun.com/products/jta/ which are required by Quartz. Select Class Files to download from the page. This release will be in form of .zip file which has class files inside it. You can extract and again package them as a .jar or leave them as it is.

3. Move Quartz jar and JTA download to WEB-INF/lib directory of your application.

4. Add following lines to your application web.xml to configure Quartz.

<servlet>
<display-name>Quartz Initializer Servlet</display-name>
<servlet-name>QuartzInitializer</servlet-name>
<servlet-class>
	org.quartz.ee.servlet.QuartzInitializerServlet
</servlet-class>
<init-param>
	<param-name>shutdown-on-unload</param-name>
	<param-value>true</param-value>
</init-param>
<init-param>
<param-name>start-scheduler-on-load</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet>
<display-name>Scheduler Servlet</display-name>
<servlet-name>SchedulerServlet</servlet-name>
<servlet-class>com.test.SchedulerServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>

In the lines above the first Servlet Element defines a new Servlet of typeorg.quartz.ee.servlet.QuartzInitializerServlet which would initialise Quartz at the time of application startup.

The second element defines a new Servlet which would be started as part of Application Init and it is the place from where we configure Quartz.

5. Here is the SchedulerServlet Implementation in which i am configuring a Cron Job. This Cron Job is similar to Unix Cron Job but the difference is that it is managed entirely in Java and does not depend on Unix Cron.

package com.test;

import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;

import com.test.Worker;

public class SchedulerServlet extends GenericServlet {

/**
* Constant to represent property for the cron expression.
*/
private static final String CRON_EXPRESSION = "0 0/15 * * * ?";

public void init(ServletConfig servletConfig) throws ServletException {

super.init(servletConfig);

// The Quartz Scheduler
Scheduler scheduler = null;

try {

// Initiate a Schedule Factory
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// Retrieve a scheduler from schedule factory
scheduler = schedulerFactory.getScheduler();
// Initiate JobDetail with job name, job group and
// executable job class
JobDetail jobDetail = new JobDetail("RetryJob",
"RetryGroup", Worker.class);
// Initiate CronTrigger with its name and group name
CronTrigger cronTrigger = new CronTrigger("cronTrigger",
"triggerGroup");
// setup CronExpression
CronExpression cexp = new CronExpression(CRON_EXPRESSION);
// Assign the CronExpression to CronTrigger
cronTrigger.setCronExpression(cexp);
// schedule a job with JobDetail and Trigger
scheduler.scheduleJob(jobDetail, cronTrigger);

// start the scheduler
scheduler.start();

} catch (Exception e) {
e.printStackTrace();
}

}

public void service(ServletRequest serveletRequest, ServletResponse servletResponse)
throws ServletException, IOException {

}

}

Code above creates a Cron Job which will call Worker ( which implements Job) and calls its execute method. There are numerous types of triggers which can be created and CronTrigger is just one example.6. Worker class which implements Job Interface

package com.test;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
* worker thread
*/
public class Worker implements Job {
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.out.println("Put task to be executed here");
}

}

Simple isn’t it.

 

~ by Vivek on November 14, 2007.

23 Responses to “Say goodbye to TimerTask for scheduling – use Quartz instead.”

  1. Great help, I spent some time trying to utilize sessionbeans and go that route based on some of the other info I read out there, and stumbled upon this and had it set up in a couple minutes. a few things I changed which may help out others is to make the interval external from the class file. You can do that by adding using the following
    #
    # Scheduler Servlet
    # SchedulerServlet
    # com.test.SchedulerServlet
    # cronExpr 0 0/1 * * * ?
    # 2
    #

    I would recommend adjusting the param-value to your desired interval. Then just change the following value:
    # CronExpression cexp = new CronExpression(CRON_EXPRESSION);
    to
    # CronExpression cexp = new CronExpression(cronExpr);
    # cronTrigger.setCronExpression(cexp);

    This will then load the parameter value from the web.xml file. This allows you to change the scheduling of the job within the xml file as opposed to the class file.

    Thanks for showing this, was a great help

  2. Thanks Christian. I wanted the example to be as simple as possible and to be easier to setup.

  3. Actually I don’t understand why do you create SchedulerFactory in com.test.SchedulerServlet class at line #35 when you can get it from the ServletContext, and why do you start the scheduler at line #53 when it is already started (as you have defined ‘start-scheduler-on-load’ property value as ‘true’ for QuartzInitializerServlet in web.xml at lines #12 and #13).

    Look at http://www.opensymphony.com/quartz/api/org/quartz/ee/servlet/QuartzInitializerServlet.html

    1) If the init parameter ‘start-scheduler-on-load’ is set to ‘true’ then the scheduler.start() method called when the servlet is first loaded. If set to ‘false’, your application will need to call the start() method before the scheduler begins to run and process jobs.

    2) A StdSchedulerFactory instance is stored into the ServletContext. You can gain access to the factory from a ServletContext instance like this:
    SchedulerFactory schedulerFactory = (SchedulerFactory) servletContext.getAttribute(QuartzInitializerServlet.QUARTZ_FACTORY_KEY);

    In addition you can use quartz.properties by putting it to the classpath and setting ‘config-file’ property for QuartzInitializerServlet in web.xml.

  4. Here is SchedulerServlet.java

    package com.test;

    import javax.servlet.*;

    import org.quartz.CronExpression;
    import org.quartz.CronTrigger;
    import org.quartz.JobDetail;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerFactory;
    import org.quartz.ee.servlet.QuartzInitializerServlet;

    import java.io.IOException;

    public class SchedulerServlet extends GenericServlet {

    /**
    * Constant to represent property for the cron expression.
    */
    private static final String CRON_EXPRESSION = “0/5 * * * * ?”;

    public void init(ServletConfig servletConfig) throws ServletException {

    super.init(servletConfig);

    // Get Servlet Context
    ServletContext servletContext = getServletContext();

    try {

    // Get Schedule Factory from servlet sontext
    SchedulerFactory schedulerFactory = (SchedulerFactory) servletContext
    .getAttribute(QuartzInitializerServlet.QUARTZ_FACTORY_KEY);

    // Retrieve Quartz Scheduler from schedule factory
    Scheduler scheduler = schedulerFactory.getScheduler();

    // Initiate JobDetail with job name, job group and executable job class
    JobDetail jobDetail = new JobDetail(“RetryJob”, “RetryGroup”, Worker.class);

    // Initiate CronTrigger with its name, group name and cron expression
    CronTrigger cronTrigger = new CronTrigger(“cronTrigger”, “triggerGroup”, CRON_EXPRESSION);

    // schedule a job with JobDetail and Trigger
    scheduler.scheduleJob(jobDetail, cronTrigger);

    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    public void service(ServletRequest serveletRequest, ServletResponse servletResponse)
    throws ServletException, IOException {

    }
    }

  5. If you want to load your quartz.properties file then you need to modify web.xml file end perform next steps:
    1) add init-param with name ‘config-file’ and value ‘quartz.properties’ for QuartzInitializerServlet;
    2) place quartz.properties file under classpath (e.g. my.war\WEB-INF\classes)

    Here is the contents of quartz.properties file

    org.quartz.scheduler.instanceName = MyQuartzScheduler
    org.quartz.scheduler.rmi.export = false
    org.quartz.scheduler.rmi.proxy = false
    org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount = 10
    org.quartz.threadPool.threadPriority = 5
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

    org.quartz.jobStore.misfireThreshold = 60000

    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

  6. @Alex : Thanks for improving this example.

  7. Hi All,

    This is a very good example of quartz API.
    I have tried it and it works during the compile time,
    but at run time when i start my jboss server i am getting following exception.

    2008-09-08 10:54:27,375 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[localhost].[/ws4ee]] Error loading org.jboss.web.tomcat.tc5.WebCtxLoader$ENCLoader@134a33d org.quartz.ee.servlet.QuartzInitializerServlet
    java.lang.ClassNotFoundException: org.quartz.ee.servlet.QuartzInitializerServlet
    at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
    at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1027)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:925)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:3857)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4118)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:759)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:739)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:524)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.apache.commons.modeler.BaseModelMBean.invoke(BaseModelMBean.java:503)
    at org.jboss.mx.server.RawDynamicInvoker.invoke(RawDynamicInvoker.java:150)
    at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
    at org.apache.catalina.core.StandardContext.init(StandardContext.java:5005)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.apache.commons.modeler.BaseModelMBean.invoke(BaseModelMBean.java:503)
    at org.jboss.mx.server.RawDynamicInvoker.invoke(RawDynamicInvoker.java:150)
    at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
    at org.jboss.web.tomcat.tc5.TomcatDeployer.performDeployInternal(TomcatDeployer.java:280)
    at org.jboss.web.tomcat.tc5.TomcatDeployer.performDeploy(TomcatDeployer.java:88)
    at org.jboss.web.AbstractWebDeployer.start(AbstractWebDeployer.java:357)
    at org.jboss.web.WebModule.startModule(WebModule.java:68)
    at org.jboss.web.WebModule.startService(WebModule.java:46)
    at org.jboss.system.ServiceMBeanSupport.jbossInternalStart(ServiceMBeanSupport.java:274)
    at org.jboss.system.ServiceMBeanSupport.jbossInternalLifecycle(ServiceMBeanSupport.java:230)
    at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
    at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
    at org.jboss.mx.server.Invocation.invoke(Invocation.java:72)
    at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:245)
    at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
    at org.jboss.system.ServiceController$ServiceProxy.invoke(ServiceController.java:943)
    at $Proxy0.start(Unknown Source)
    at org.jboss.system.ServiceController.start(ServiceController.java:428)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
    at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
    at org.jboss.mx.server.Invocation.invoke(Invocation.java:72)
    at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:245)
    at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
    at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:176)
    at $Proxy29.start(Unknown Source)
    at org.jboss.web.AbstractWebContainer.start(AbstractWebContainer.java:400)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
    at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
    at org.jboss.mx.interceptor.AbstractInterceptor.invoke(AbstractInterceptor.java:118)
    at org.jboss.mx.server.Invocation.invoke(Invocation.java:74)
    at org.jboss.mx.interceptor.ModelMBeanOperationInterceptor.invoke(ModelMBeanOperationInterceptor.java:127)
    at org.jboss.mx.interceptor.DynamicInterceptor.invoke(DynamicInterceptor.java:80)
    at org.jboss.mx.server.Invocation.invoke(Invocation.java:74)
    at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:245)
    at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
    at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:176)
    at $Proxy30.start(Unknown Source)

  8. @Ankit : The exception trace above means that Container is not able to locate Quartz Class file which is present in Quartz API download jar. Check if the jar file is present in EAR/WAR. If not add to WEB-INF/lib of war and APP-INF/lib of ear.

  9. Good article mate,way to go

  10. To include Quartz as a dependency in your project using Maven, you can forgo all the downloading and copying by hand. Add this dependency element to the dependencies section of your pom:

    quartz
    quartz
    1.5.2

    Scheduling functionality can alternatively be found in the Timer (cron-like) and Work Manager (thread-like) commonj APIs.

  11. Neat, it stripped my markup. Here’s a link for that dependency: http://www.mvnrepository.com/artifact/quartz/quartz/1.5.2

  12. @Steve Have you tried Apache Ivy yet? Configuration is small and enables full control on dependencies by using set of configurations.

  13. @Vivek I have not, but I’ll certainly look into it. I’m completely new to scheduling in a J2EE environment, so I appreciate the pointers!

  14. Thanks a lot, you have saved me from many hours/days of configuration problems after I found quartz docs to be lacking 🙂

  15. This was very helpful to me – thanks to all who posted here.

  16. Found similiar example in
    http://satishanu-in-jax.blogspot.com/

  17. Very helpful article altogether with comments. Thanks a lot!

  18. […] view plaincopy to clipboardprint? […]

  19. Thanks a lot. Although this thread is not the latest: it solved my problem.
    Now my Tomcat is scheduled to an interval of 60 seconds and is working fin.

    Bye Frank

  20. Hi,
    Thanks for your time.Can I able to call a servlet from this “Worker.java” agian. or Can I make this worker.java as aslo a servlet.

    • @Latha A servlet can be invoked from Worker i.e. You can make calls to a particular servlet using Java URLConnection. If you are looking for Navigating to another servlet, then it is something I am not aware of. Hope this helps

  21. Nice post….It has really save me so much time.

    @Vivek. Nice pointer.

    @Alex. Thanks for improving the HOWTO.

  22. fantastic. I will implement this in my project. Thanks a lot

Leave a comment