Quartz Scheduler Tutorial
In this article, we will look into an example of Quartz Scheduler. We will go through the setup, scheduler factory, scheduler, job, job details, jobDataMap, triggers, and listeners.
If your application has tasks that need to occur at given moments in time, or if your system has recurring maintenance jobs then Quartz may be your ideal solution.
Before we start with the example, let’s look into the basics.
1. What is Quartz?
Quartz is a Java open source job-scheduling system capable of scheduling and executing jobs. In this example, we use the latest stable Quartz available which is version 2.2.1.
- You can download Quartz from http://quartz-scheduler.org/downloads
Ok, we know what is Quartz but what is job here? A job is a Java class containing the task to be executed but how do we schedule it?. We schedule the job using the trigger which defines when to execute the job. We now know the basic components that Quartz is made up of. Once we start with an example you will know their roles. Let’s start with the Quartz setup.
2. Quartz Setup
In this example, we will use Maven as the build tool so all you need to do is add the below dependency to pom.xml which looks like below.
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javacodegeeks.camel</groupId> <artifactId>camelHelloWorld</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> </dependencies> </project>
3. Quartz Scheduler Example
Our example consists of create a schedule, a job that needs to be executed, a trigger that defines when to execute and then finally scheduling the job.
SchedulerFactory
– Before you can use the Scheduler, it needs to be instantiated. To do this, we use aSchedulerFactory
.SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler
– Use the aboveSchedulerFactory
instance to instantiateScheduler
.Scheduler scheduler = schedFact.getScheduler();
- Star the Scheduler – Once the scheduler is instantiated, it needs to be started. Note triggers will not fire (and therefore, jobs will not execute) until the scheduler has been started. Nor will they fire while the scheduler is in the paused state.
scheduler.start();
Job
– Next we create a job implementing Quartz interfaceorg.quartz.Job
. The actual execution code goes into methodexecute
.JobDataMap
provides a mechanism for ‘instance member data’JobDetail
– Conveys the detail properties of a givenJob
instance.Trigger
– A component that defines the schedule upon which a given Job will be executed.JobBuilder
– This is used to define/buildJobDetail
instances, which define instances of Jobs. We passMyJob.class
to the builder. We also pass the required job data as an object ofJobDataMap
.JobBuilder jobBuilder = JobBuilder.newJob(MyJob.class); jobDataMap data = new JobDataMap(); data.put("latch", this); JobDetail jobDetail = jobBuilder.usingJobData("example", "com.javacodegeeks.quartz.QuartzSchedulerExample") .usingJobData(data) .withIdentity("myJob", "group1") .build();
TriggerBuilder
– used to define/build Trigger instances. Here we create a simple schedule that repeats for certain count, each one repeating after certain interval of time.Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withRepeatCount(repeatCount) .withIntervalInSeconds(2)) .build();
QuartzSchedulerExample:
package com.javacodegeeks.quartz; import java.util.concurrent.CountDownLatch; import org.quartz.JobBuilder; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; public class QuartzSchedulerExample implements ILatch { private int repeatCount = 3; private CountDownLatch latch = new CountDownLatch(repeatCount + 1); public static void main(String[] args) throws Exception { QuartzSchedulerExample quartzSchedulerExample = new QuartzSchedulerExample(); quartzSchedulerExample.fireJob(); } public void fireJob() throws SchedulerException, InterruptedException { SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); Scheduler scheduler = schedFact.getScheduler(); scheduler.start(); // define the job and tie it to our HelloJob class JobBuilder jobBuilder = JobBuilder.newJob(MyJob.class); JobDataMap data = new JobDataMap(); data.put("latch", this); JobDetail jobDetail = jobBuilder.usingJobData("example", "com.javacodegeeks.quartz.QuartzSchedulerExample") .usingJobData(data) .withIdentity("myJob", "group1") .build(); // Trigger the job to run now, and then every 40 seconds Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withRepeatCount(repeatCount) .withIntervalInSeconds(2)) .build(); // Tell quartz to schedule the job using our trigger scheduler.scheduleJob(jobDetail, trigger); latch.await(); System.out.println("All triggers executed. Shutdown scheduler"); scheduler.shutdown(); } public void countDown() { latch.countDown(); } }
ILatch:
package com.javacodegeeks.quartz; public interface ILatch { void countDown(); }
In our job, we simply print some basic stuff as well we the job data received.
MyJob:
package com.javacodegeeks.quartz; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class MyJob implements Job { private static int count; public void execute(JobExecutionContext jobContext) throws JobExecutionException { System.out.println("--------------------------------------------------------------------"); System.out.println("MyJob start: " + jobContext.getFireTime()); JobDetail jobDetail = jobContext.getJobDetail(); System.out.println("Example name is: " + jobDetail.getJobDataMap().getString("example")); System.out.println("MyJob end: " + jobContext.getJobRunTime() + ", key: " + jobDetail.getKey()); System.out.println("MyJob next scheduled time: " + jobContext.getNextFireTime()); System.out.println("--------------------------------------------------------------------"); ILatch latch = (ILatch) jobDetail.getJobDataMap().get("latch"); latch.countDown(); count++; System.out.println("Job count " + count); if (count == 2) { throw new RuntimeException("Some RuntimeException!"); } if (count == 4) { throw new JobExecutionException("Some JobExecutionException!"); } } }
You can see here, the job repeats itself for three times after executing the first time. There is an interval of 2 seconds between each execution.
4. JobExecutionException
The only type of exception that Quartz recommends to use in execute()
method is the RuntimeException
and JobExecutionException
. If there are chances of any other exception then you should make sure the code in execute()
is wrapped with a ‘trycatch’ block.
You can further configure JobExecutionException
using methods like setRefireImmediately()
and setUnscheduleFiringTrigger()
.
MyJob:
package com.javacodegeeks.quartz; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class MyJob implements Job { private static int count; public void execute(JobExecutionContext jobContext) throws JobExecutionException { ... if (count == 2) { throw new RuntimeException("Some RuntimeException!"); } if (count == 4) { throw new JobExecutionException("Some JobExecutionException!"); } } }
5. Run the Example
As you can see the job fires for four times. After the first time, it repeats three more times and then shuts down. Between each job there is an interval of 2 seconds.
Once all the jobs are run, we shutdown the scheduler.
Output:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. -------------------------------------------------------------------- MyJob start: Tue Sep 08 22:19:13 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: Tue Sep 08 22:19:15 IST 2015 -------------------------------------------------------------------- -------------------------------------------------------------------- MyJob start: Tue Sep 08 22:19:15 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: Tue Sep 08 22:19:17 IST 2015 -------------------------------------------------------------------- -------------------------------------------------------------------- MyJob start: Tue Sep 08 22:19:17 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: Tue Sep 08 22:19:19 IST 2015 -------------------------------------------------------------------- -------------------------------------------------------------------- MyJob start: Tue Sep 08 22:19:19 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: null -------------------------------------------------------------------- All triggers executed. Shutdown scheduler
6. Triggers
There are different types of triggers that you can select based on your scheduling needs. The two most common types are simple triggers and cron triggers. Next two sections, we will show examples of simple as well as cron triggers.
7. Simple Triggers
We already have seen example of simple trigger. We will use it if the job needs to execute exactly once at a specific moment in time, or at a specific moment in time followed by repeats at a specific interval then simple trigger should be OK. For example,
Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withRepeatCount(repeatCount) .withIntervalInSeconds(2)) .build();
You can also be specific with time using DateBuilder
. Like the below simple trigger is set to start today at a specific hr/min/sec.
Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startAt(DateBuilder.todayAt(10, 20, 20)) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withRepeatCount(repeatCount) .withIntervalInSeconds(2)) .build();
Suppose you want the trigger to the job to execute after every 2 second interval and want this to be repeated forever.
Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startAt(DateBuilder.todayAt(10, 20, 20)) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(2) .repeatForever()) .build();
8. Cron Triggers
CronTrigger
instances are built using TriggerBuilder
and another helper class called CronScheduleBuilder
which we can use to set the CronTrigger-specific properties. Cron-Expressions are used to configure instances of CronTrigger. Cron-Expressions are strings that are actually made up of seven sub-expressions, that describe individual details of the schedule. These sub-expression are separated with white-space, and represent:
- Seconds
- Minutes
- Hours
- Day-of-Month
- Month
- Day-of-Week
- Year (optional field)
Example would be 0 15 10 * * ? * – Fire at 10:15am every day. You can also use some special characters, listed few important ones below:
- * every minute if * is placed in minute field
- ? useful when you need to specify something in one of the two fields in which the character is allowed, but not the other.
- – used to specify ranges.
- , to specify additional values
- / used to specify increments. For example, “0/15” in the seconds field means “the seconds 0, 15, 30, and 45″.
In the below Example of Crone trigger, the trigger is setup to fire after a min from the current date time.
QuartzSchedulerCronTriggerExample:
package com.javacodegeeks.quartz; import java.util.Calendar; import java.util.Date; import java.util.concurrent.CountDownLatch; import org.quartz.CronScheduleBuilder; import org.quartz.DateBuilder; import org.quartz.JobBuilder; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; public class QuartzSchedulerCronTriggerExample implements ILatch { private CountDownLatch latch = new CountDownLatch(1); public static void main(String[] args) throws Exception { QuartzSchedulerCronTriggerExample quartzSchedulerExample = new QuartzSchedulerCronTriggerExample(); quartzSchedulerExample.fireJob(); } public void fireJob() throws SchedulerException, InterruptedException { SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); Scheduler scheduler = schedFact.getScheduler(); scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener(scheduler)); scheduler.start(); // define the job and tie it to our HelloJob class JobBuilder jobBuilder = JobBuilder.newJob(MyJob.class); JobDataMap data = new JobDataMap(); data.put("latch", this); JobDetail jobDetail = jobBuilder.usingJobData("example", "com.javacodegeeks.quartz.QuartzSchedulerListenerExample") .usingJobData(data) .withIdentity("myJob", "group1") .build(); Calendar rightNow = Calendar.getInstance(); int hour = rightNow.get(Calendar.HOUR_OF_DAY); int min = rightNow.get(Calendar.MINUTE); System.out.println("Current time: " + new Date()); // Fire at curent time + 1 min every day Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startAt(DateBuilder.todayAt(10, 20, 20)) .withSchedule(CronScheduleBuilder.cronSchedule("0 " + (min + 1) + " " + hour + " * * ? *")) .build(); // Tell quartz to schedule the job using our trigger scheduler.scheduleJob(jobDetail, trigger); latch.await(); System.out.println("All triggers executed. Shutdown scheduler"); scheduler.shutdown(); } public void countDown() { latch.countDown(); } }
Output:
Current time: Wed Sep 09 18:22:38 IST 2015 Job added: myJob Job scheduled: myTrigger -------------------------------------------------------------------- MyJob start: Wed Sep 09 18:23:00 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerListenerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: Thu Sep 10 18:23:00 IST 2015 -------------------------------------------------------------------- Job count 1
9. Scheduler Listener
Its time to add a scheduler listener.
A SchedulerListener implements org.quar.SchedulerListener
interface. It receives notification of events within the Scheduler itself, for example:
- The addition of a job or trigger
- The removal of a job or trigger
- An error within the Scheduler
- The shutdown of the Scheduler
SchedulerListeners are registered with the scheduler’s ListenerManager.
scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener(scheduler));
Let’s first create our own SchedulerListener.
MySchedulerListener:
package com.javacodegeeks.quartz; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerListener; import org.quartz.Trigger; import org.quartz.TriggerKey; public class MySchedulerListener implements SchedulerListener { private final Scheduler scheduler; public MySchedulerListener(Scheduler scheduler) { this.scheduler = scheduler; } public void jobScheduled(Trigger trigger) { System.out.println("Job scheduled: " + trigger.getKey().getName()); } public void jobUnscheduled(TriggerKey triggerKey) { System.out.println("Job Unscheduled: " + triggerKey.getName()); } public void triggerFinalized(Trigger trigger) { System.out.println("Job trigger finalized: " + trigger.getKey().getName()); } public void triggerPaused(TriggerKey triggerKey) { System.out.println("Job trigger paused: " + triggerKey.getName()); } public void triggersPaused(String triggerGroup) { System.out.println("Job triggers paused for trigger group: " + triggerGroup); } public void triggerResumed(TriggerKey triggerKey) { System.out.println("Job triggers resumed for trigger: " + triggerKey); } public void triggersResumed(String triggerGroup) { System.out.println("Job triggers resumed for trigger group: " + triggerGroup); } public void jobAdded(JobDetail jobDetail) { System.out.println("Job added: " + jobDetail.getKey().getName()); } public void jobDeleted(JobKey jobKey) { System.out.println("Job deleted: " + jobKey.getName()); } public void jobPaused(JobKey jobKey) { System.out.println("Jobs paused for job: " + jobKey); } public void jobsPaused(String jobGroup) { System.out.println("Jobs paused for job group: " + jobGroup); } public void jobResumed(JobKey jobKey) { System.out.println("Job resumed: " + jobKey.getName()); } public void jobsResumed(String jobGroup) { System.out.println("Jobs resumed for job group: " + jobGroup); } public void schedulerError(String msg, SchedulerException cause) { System.out.println("Scheduler Error: " + cause); } public void schedulerInStandbyMode() { try { System.out.println("Scheduler put in standby mode: " + scheduler.getSchedulerName()); } catch (SchedulerException e) { System.out.println("Error getting scheduler name" + e); } } public void schedulerStarted() { try { System.out.println("Scheduler started: " + scheduler.getSchedulerName()); } catch (SchedulerException e) { System.out.println("Error getting scheduler name" + e); } } public void schedulerShutdown() { try { System.out.println("Scheduler shutdown: " + scheduler.getSchedulerName()); } catch (SchedulerException e) { System.out.println("Error getting scheduler name" + e); } } public void schedulerShuttingdown() { try { System.out.println("Scheduler shutting down: " + scheduler.getSchedulerName()); } catch (SchedulerException e) { System.out.println("Error getting scheduler name" + e); } } public void schedulingDataCleared() { try { System.out.println("Scheduler data cleared: " + scheduler.getSchedulerName()); } catch (SchedulerException e) { System.out.println("Error getting scheduler name" + e); } } public void schedulerStarting() { try { System.out.println("Scheduler starting: " + scheduler.getSchedulerName()); } catch (SchedulerException e) { System.out.println("Error getting scheduler name" + e); } } }
We will register the scheduler listener and then schedule the job.
QuartzSchedulerListenerExample:
package com.javacodegeeks.quartz; import java.util.concurrent.CountDownLatch; import org.quartz.JobBuilder; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; public class QuartzSchedulerListenerExample implements ILatch { private int repeatCount = 3; private CountDownLatch latch = new CountDownLatch(repeatCount + 1); public static void main(String[] args) throws Exception { QuartzSchedulerListenerExample quartzSchedulerExample = new QuartzSchedulerListenerExample(); quartzSchedulerExample.fireJob(); } public void fireJob() throws SchedulerException, InterruptedException { SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); Scheduler scheduler = schedFact.getScheduler(); scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener(scheduler)); scheduler.start(); // define the job and tie it to our HelloJob class JobBuilder jobBuilder = JobBuilder.newJob(MyJob.class); JobDataMap data = new JobDataMap(); data.put("latch", this); JobDetail jobDetail = jobBuilder.usingJobData("example", "com.javacodegeeks.quartz.QuartzSchedulerListenerExample") .usingJobData(data) .withIdentity("myJob", "group1") .build(); // Trigger the job to run now, and then every 40 seconds Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withRepeatCount(repeatCount) .withIntervalInSeconds(2)) .build(); // Tell quartz to schedule the job using our trigger scheduler.scheduleJob(jobDetail, trigger); latch.await(); System.out.println("All triggers executed. Shutdown scheduler"); scheduler.shutdown(); } public void countDown() { latch.countDown(); } }
As you can see below, we info like ‘Scheduler Starting…’ etc which are from scheduler listener.
Output:
Scheduler starting: DefaultQuartzScheduler Scheduler started: DefaultQuartzScheduler Job added: myJob Job scheduled: myTrigger -------------------------------------------------------------------- MyJob start: Wed Sep 09 15:10:05 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerListenerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: Wed Sep 09 15:10:07 IST 2015 -------------------------------------------------------------------- Job count 1 -------------------------------------------------------------------- MyJob start: Wed Sep 09 15:10:07 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerListenerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: Wed Sep 09 15:10:09 IST 2015 -------------------------------------------------------------------- Job count 2 Scheduler Error: org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: java.lang.RuntimeException: Some RuntimeException!] -------------------------------------------------------------------- MyJob start: Wed Sep 09 15:10:09 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerListenerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: Wed Sep 09 15:10:11 IST 2015 -------------------------------------------------------------------- Job count 3 -------------------------------------------------------------------- MyJob start: Wed Sep 09 15:10:11 IST 2015 Example name is: com.javacodegeeks.quartz.QuartzSchedulerListenerExample MyJob end: -1, key: group1.myJob MyJob next scheduled time: null -------------------------------------------------------------------- Job count 4 All triggers executed. Shutdown scheduler Job trigger finalized: myTrigger Scheduler put in standby mode: DefaultQuartzScheduler Scheduler shutting down: DefaultQuartzScheduler Job deleted: myJob Scheduler shutdown: DefaultQuartzScheduler
10. Download the Eclipse Project
This was a tutorial about Quartz Scheduler.
You can download the full source code of this example here: quartzSchedulerExample.zip