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.
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
Christian said this on August 1, 2008 at 12:19 pm |
Thanks Christian. I wanted the example to be as simple as possible and to be easier to setup.
Vivek said this on August 5, 2008 at 11:50 am |
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.
Alex said this on August 30, 2008 at 12:53 am |
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 {
}
}
Alex said this on August 30, 2008 at 1:06 am |
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
Alex said this on August 30, 2008 at 1:15 am |
@Alex : Thanks for improving this example.
Vivek said this on August 30, 2008 at 6:10 am |
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)
Ankit said this on September 8, 2008 at 6:16 am |
@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.
Vivek said this on September 8, 2008 at 6:52 am |
Good article mate,way to go
Sivananda said this on September 20, 2008 at 2:46 am |
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.
Steve said this on October 9, 2008 at 1:06 pm |
Neat, it stripped my markup. Here’s a link for that dependency: http://www.mvnrepository.com/artifact/quartz/quartz/1.5.2
Steve said this on October 9, 2008 at 1:07 pm |
@Steve Have you tried Apache Ivy yet? Configuration is small and enables full control on dependencies by using set of configurations.
Vivek said this on October 9, 2008 at 9:31 pm |
@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!
Steve said this on October 10, 2008 at 3:58 am |
Thanks a lot, you have saved me from many hours/days of configuration problems after I found quartz docs to be lacking 🙂
foo said this on October 29, 2008 at 4:10 am |
This was very helpful to me – thanks to all who posted here.
Mike said this on December 30, 2008 at 1:29 am |
Found similiar example in
http://satishanu-in-jax.blogspot.com/
anonymous said this on January 9, 2009 at 8:05 am |
Very helpful article altogether with comments. Thanks a lot!
Nena said this on February 3, 2009 at 10:37 pm |
[…] view plaincopy to clipboardprint? […]
TimerTask for scheduling - use Quartz instead. | everylinkforyou said this on March 12, 2009 at 12:50 am |
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
Frank Hartel said this on November 13, 2010 at 10:38 am |
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 said this on May 11, 2011 at 9:35 am |
@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
Vivek said this on May 12, 2011 at 5:43 pm |
Nice post….It has really save me so much time.
@Vivek. Nice pointer.
@Alex. Thanks for improving the HOWTO.
Albert said this on June 1, 2011 at 9:41 pm |
fantastic. I will implement this in my project. Thanks a lot
Mohan said this on December 7, 2011 at 1:45 pm |