Mastering Delayed Tasks: From Quartz to Redis and RabbitMQ

This article explores various techniques for implementing delayed tasks in Java, comparing Quartz database polling, JDK DelayQueue, Netty's HashedWheelTimer, Redis sorted sets, and RabbitMQ delayed queues, detailing their implementations, advantages, drawbacks, and practical code examples for handling order timeouts.

Architect's Guide
Architect's Guide
Architect's Guide
Mastering Delayed Tasks: From Quartz to Redis and RabbitMQ

In development, delayed tasks such as automatically canceling an order after 30 minutes or sending an SMS after 60 seconds are common requirements.

These tasks are called delayed tasks and differ from scheduled tasks in several ways.

Scheduled tasks have a fixed trigger time; delayed tasks do not.

Scheduled tasks have execution cycles; delayed tasks execute once after an event.

Scheduled tasks often batch multiple jobs; delayed tasks usually handle a single job.

Solution Analysis

(1) Database Polling (Quartz)

Idea: a thread periodically scans the database for overdue orders and updates or deletes them.

Implementation using Quartz. Maven dependency:

<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.2.2</version>
</dependency>

Demo job prints a message while scanning:

public class MyJob implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("Scanning database...");
    }
    public static void main(String[] args) throws Exception {
        JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("job1", "group1").build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group3")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(3).repeatForever())
                .build();
        Scheduler scheduler = new StdSchedulerFactory().getScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}

Pros: simple, supports clustering. Cons: high memory consumption, latency up to the scan interval, heavy DB load for millions of orders.

(2) JDK DelayQueue

Idea: use java.util.concurrent.DelayQueue, which holds elements implementing Delayed and releases them when their delay expires.

Implementation of OrderDelay:

public class OrderDelay implements Delayed {
    private String orderId;
    private long timeout;
    OrderDelay(String orderId, long timeout) {
        this.orderId = orderId;
        this.timeout = timeout + System.nanoTime();
    }
    public int compareTo(Delayed other) { /* omitted for brevity */ }
    public long getDelay(TimeUnit unit) {
        return unit.convert(timeout - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
    public void print() {
        System.out.println(orderId + " order will be deleted...");
    }
}

Demo runs five orders with a 3‑second delay, printing each order ID after the delay:

public class DelayQueueDemo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("00000001","00000002","00000003","00000004","00000005");
        DelayQueue<OrderDelay> queue = new DelayQueue<>();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            queue.put(new OrderDelay(list.get(i),
                TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS)));
            OrderDelay od = queue.take();
            od.print();
            System.out.println("After " + (System.currentTimeMillis() - start) + " Milliseconds");
        }
    }
}

Pros: high efficiency, low trigger latency. Cons: data lost on server restart, difficult cluster expansion, possible OOM for massive pending orders, higher code complexity.

(3) Netty HashedWheelTimer

Idea: use Netty's HashedWheelTimer, a time‑wheel algorithm similar to a clock.

Dependency:

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-all</artifactId>
  <version>4.1.24.Final</version>
</dependency>

Demo task prints a message after 5 seconds:

public class HashedWheelTimerTest {
    static class MyTimerTask implements TimerTask {
        boolean flag = true;
        public void run(Timeout timeout) {
            System.out.println("Deleting order...");
            flag = false;
        }
    }
    public static void main(String[] args) {
        MyTimerTask task = new MyTimerTask();
        Timer timer = new HashedWheelTimer();
        timer.newTimeout(task, 5, TimeUnit.SECONDS);
        int i = 1;
        while (task.flag) {
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            System.out.println(i + " seconds passed");
            i++;
        }
    }
}

Pros: efficient, low latency. Cons: similar restart and clustering issues as DelayQueue.

(4) Redis Sorted Set (ZSET)

Idea: store order ID as member and expiration timestamp as score in a Redis ZSET, periodically scan the first element.

Key commands:

ZADD key score member

ZRANGE key start stop WITHSCORES

ZSCORE key member

ZREM key member

Consumer checks if current time >= score, removes the member, and processes the order.

public class AppTest {
    private static final String ADDR = "127.0.0.1";
    private static final int PORT = 6379;
    private static JedisPool jedisPool = new JedisPool(ADDR, PORT);
    public void productionDelayMessage() {
        for (int i = 0; i < 5; i++) {
            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.SECOND, 3);
            int ts = (int) (cal.getTimeInMillis() / 1000);
            jedisPool.getResource().zadd("OrderId", ts, "OID000000" + i);
        }
    }
    public void consumerDelayMessage() {
        while (true) {
            Set<Tuple> items = jedisPool.getResource().zrangeWithScores("OrderId", 0, 0);
            if (items.isEmpty()) {
                try { Thread.sleep(500); } catch (InterruptedException e) {}
                continue;
            }
            Tuple t = items.iterator().next();
            int score = (int) t.getScore();
            int now = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
            if (now >= score) {
                Long removed = jedisPool.getResource().zrem("OrderId", t.getElement());
                if (removed != null && removed > 0) {
                    System.out.println("Consumed order " + t.getElement());
                }
            }
        }
    }
}

Pros: reliable, easy to scale horizontally, accurate timing. Cons: requires Redis maintenance; Redis Pub/Sub is fire‑and‑forget, so notifications can be lost on client disconnect.

(5) RabbitMQ Delayed Queue

RabbitMQ supports per‑message TTL (x-message-ttl) and dead‑letter exchange to emulate delayed messages.

Advantages: high efficiency, leverages RabbitMQ's clustering for easy horizontal scaling, messages can be persisted for reliability.

Drawbacks: adds operational complexity and cost due to the need to manage RabbitMQ.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaRedisSchedulingRabbitMQdelayed tasksQuartz
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.