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.