Scheduling in Java using Quartz
Quartz is an open source job-scheduling framework used for setting up schedules.
- Quartz Jar/Dependency
The main Quartz library is named quartz-xxx.jar (where xxx is a version number). In order to use any of Quartz’s features, this jar must be located on your application’s classpath.
We will use Maven dependency to include this in our project
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
2. Quartz APIs
Quartz is based on a multi-threaded architecture. When started, the framework initialises a set of worker threads that are used by the Scheduler to execute Jobs.
This helps scheduler to run many Jobs concurrently. It also relies on a loosely coupled set of ThreadPool management components for managing the thread environment.
Important Concepts of Quartz Scheduler
- Scheduler — Scheduler is basic api and comes with life cycle. Scheduler needs to be started and stopped. We can do scheduling related tasks like add, delete or pause jobs and triggers through scheduler.
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
Scheduler will not execute jobs until it has been started (though they can be scheduled before start())
scheduler.start();
- Job — Job is actual task which we want to perform in schedule
public class PrintPropsJob implements Job {
public PrintPropsJob() {
// Instances of Job must have a public no-argument constructor.
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
JobDataMap data = context.getMergedJobDataMap();
System.out.println("someProp = " + data.getString("someProp"));
}
}
When job trigger fires, execute() method gets invoked by one of the worker thread.
JobExecutionContext argument to execute() contains all necessary information to run this schedule like tigger data, runtime data, job details, etc.
JobDataMap can hold data which you want to use during execution of job.
- JobDetail — used to define instances of Jobs, its definition of job instance.
The JobDetail object is created by the Quartz client (your program) at the time the Job is added to the scheduler. It contains various property settings for the Job, as well as a JobDataMap, which can be used to store state information for a given instance of your job class. It is essentially the definition of the job instance
// Define job instance
JobDetail job1 = newJob(MyJobClass.class)
.withIdentity("job1", "group1")
.usingJobData("someProp", "someValue")
.build();
- Trigger — a important component that determines the schedule upon which a given Job will be performed
// Define job instance
JobDetail job1 = newJob(ColorJob.class)
.withIdentity("job1", "group1")
.build();
// Define a Trigger that will fire "now", and not repeat
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.build();
// Schedule the job with the trigger
sched.scheduleJob(job, trigger);
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startNow()
.withSchedule(dailyAtHourAndMinute(15, 0)) // fire every day at 15:00
.build();
There are various type of triggers like CronTrigger, SimpleTrigger and CalendarInterval Trigger. If you want a trigger that always fires at a certain time of day, use CronTrigger or CalendarIntervalTrigger because they can preserve the fire time’s time of day across daylight savings time changes.
SimpleTrigger is handy if you need ‘one-shot’ execution (just single execution of a job at a given moment in time), or if you need to fire a job at a given time, and have it repeat N times, with a delay of T between executions. CronTrigger is useful if you wish to have triggering based on calendar-like schedules — such as “every Friday, at noon” or “at 10:15 on the 10th day of every month.”
- JobBuilder — used to build JobDetail instances, which define instances of Jobs
- TriggerBuilder — used to build Trigger instances
3. Demo
Create a Job : Our job below will print message “This is job A”
You can practically do anything you want inside this method.
package com.techflow.api.scheduler;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
public class JobA implements Job {
public void execute(JobExecutionContext arg0) {
System.out.println("This is the job A");
}
}
Create a Scheduler :
- First we created a Scheduler instance using SchedulerFactory
- Instantiate Jobs with JobBuilder, in simple Job below we also used JobData for sending data to job.
- Instantiate Triggers with TriggerBuilder, we can define whatever type that suits our use case.
- Next we added jobs with trigger in schedule, this is done with help of scheduleJob() method.
- I have added job listener and trigger listener below, WIth this we can done some task before job execution or when job is skipped, when trigger is skipped, etc. (Please check more details in my git repo here)
- Finally we can start our scheduler, basically we have all our jobs added in memory redy for getting fired.
package com.techflow.api.scheduler;
import org.apache.commons.lang3.time.DateUtils;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;
import java.util.Date;
import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;
public class OrderScheduler {
public static void main(String args[]) {
SchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
try {
Scheduler scheduler = stdSchedulerFactory.getScheduler();
//Define Schedules
JobDetail simpleJob = JobBuilder.newJob(SimpleJob.class).withIdentity("myJob", "group1").usingJobData("jobSays", "Hello World!").usingJobData("myFloatValue", 3.141f).build();
Trigger simpleJobTrigger = TriggerBuilder.newTrigger().withIdentity("myTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(40).repeatForever()).build();
JobDetail jobA = JobBuilder.newJob(JobA.class).withIdentity("jobA", "group2").build();
Trigger triggerA = TriggerBuilder.newTrigger().withIdentity("triggerA", "group2").startNow().withPriority(15).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(40).repeatForever()).build();
JobDetail jobB = JobBuilder.newJob(JobB.class).withIdentity("jobB", "group2").build();
Trigger triggerB = TriggerBuilder.newTrigger().withIdentity("triggerB", "group2").startNow().withPriority(10).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(20).repeatForever()).build();
JobDetail job3 = JobBuilder.newJob(JobB.class).withIdentity("job3", "group3").build();
Trigger misFiredTrigger3 = TriggerBuilder.newTrigger().withIdentity("misFiredTriggerA","group3")
.startAt(DateUtils.addSeconds(new Date(), -10))
.build();
TriggerKey triggerKey = new TriggerKey("job4", "group3");
JobDetail job4 = JobBuilder.newJob(JobB.class).withIdentity("job4", "group3").build();
Trigger misFiredTrigger4 = TriggerBuilder.newTrigger().withIdentity(triggerKey)
.startAt(DateUtils.addSeconds(new Date(), -100))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withMisfireHandlingInstructionFireNow())
.build();
//Add Schedules
scheduler.scheduleJob(simpleJob, simpleJobTrigger);
scheduler.scheduleJob(jobA, triggerA);
scheduler.scheduleJob(jobB, triggerB);
scheduler.scheduleJob(job3, misFiredTrigger3);
scheduler.scheduleJob(job4, misFiredTrigger4);
//Jobs Listener
MyJobListener myJobListener = new MyJobListener();
MyTriggersListener myTriggersListener = new MyTriggersListener();
scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("group3"));
scheduler.getListenerManager().addTriggerListener(myTriggersListener, KeyMatcher.keyEquals(triggerKey));
//Start Schedule
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}