June 22, 2017 Webby

A simple Quartz Scheduler facade

Quartz is no doubt the most popular open source Java library for job scheduling. It’s been around for many years and includes advanced features like clustering.Its API is a bit clunky though, especially for simple cases where you just want to schedule a job using a cron expression. As you can see from its tutorial you need not just a Job class but also a JobDetail and a Trigger before you can pass it to the scheduler.

Additionally, and this is the main pain point, you need to pass a Job class, not an instance, so your Job implementation needs a default no-arg constructor, and you can’t prepare the instance by e.g. invoking setter methods before scheduling it. Parameters and state can be passed using a JobDataMap (see example) but that’s not very intuitive and doesn’t make for clean code.

For this reason I wrote a little facade called Cron that lets you schedule a job by passing a Job instance and a cron expression, nothing more. Here’s a trivial example of how you can execute a job every 5 seconds:

Cron cron = new Cron(new StdSchedulerFactory());
Job job = new Job() {
    public void execute(JobExecutionContext context) {
        System.out.println("job executed");
    }
};
cron.schedule(job, "*/5 * * * * ?");

cron.start();
System.in.read();
cron.shutdown();

Here’s the Cron class. Internally it uses a custom JobFactory to recycle the provided job instances, rather than creating new instances from Job classes like Quartz does by default.

public class Cron {

    private static class JobRegistry implements JobFactory {

        private final Map<String, Job> jobByName = new HashMap<String, Job>();

        public void register(String name, Job job) {
            jobByName.put(name, job);
        }

        @Override
        public Job newJob(TriggerFiredBundle bundle) throws SchedulerException {
            return jobByName.get(bundle.getJobDetail().getName());
        }
    }

    private final Scheduler quartz;
    private final JobRegistry jobFactory = new JobRegistry();
    private final AtomicInteger sequence = new AtomicInteger();

    public Cron(SchedulerFactory schedulerFactory) throws SchedulerException {
        quartz = schedulerFactory.getScheduler();
        quartz.setJobFactory(jobFactory);
    }

    public void schedule(Job job, String expression) throws ParseException, SchedulerException {
        int id = sequence.getAndIncrement();
        jobFactory.register("job" + id, job);
        JobDetail detail = new JobDetail("job" + id, job.getClass());
        CronTrigger trigger = new CronTrigger("trigger" + id, null, expression);
        quartz.scheduleJob(detail, trigger);
    }

    public void start() throws SchedulerException {
        quartz.start();
    }

    public void shutdown() throws SchedulerException {
        quartz.shutdown();
    }
}

Pretty straightforward. Note that it’s not intended for use with clustering and a database, only with a RAMJobStore.

Incidentally, if you’re using Quartz v1.7.3 or higher you may want to disable its new “update check” unwanted feature, or it will make a periodic web request to the Terracotta servers.