11 Ways to Implement Delayed Tasks in Java: From DelayQueue to Quartz

This article surveys eleven Java techniques for delayed task execution—including DelayQueue, Timer, ScheduledThreadPoolExecutor, RocketMQ, RabbitMQ, Redis key‑expiration, Redisson, Netty, Hutool, Quartz and simple polling—explaining their core principles, code examples, and when each approach is appropriate.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
11 Ways to Implement Delayed Tasks in Java: From DelayQueue to Quartz

Delay tasks are common in everyday development, such as order timeout cancellation or automatic receipt confirmation. This article reviews eleven implementation methods, discussing their principles and suitable scenarios.

DelayQueue

DelayQueue is a JDK API that provides a delayed queue. The generic type must implement the Delayed interface, which extends Comparable. The getDelay method returns the remaining time before execution; a negative value means the task is ready. The compareTo method orders tasks so the earliest one is at the head of the queue.

Demo

@Getter
public class SanYouTask implements Delayed {
    private final String taskContent;
    private final Long triggerTime;
    public SanYouTask(String taskContent, Long delayTime) {
        this.taskContent = taskContent;
        this.triggerTime = System.currentTimeMillis() + delayTime * 1000;
    }
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(triggerTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }
    @Override
    public int compareTo(Delayed o) {
        return this.triggerTime.compareTo(((SanYouTask) o).triggerTime);
    }
}

SanYouTask implements Delayed. Its constructor parameters are:

taskContent: the specific content of the delayed task

delayTime: delay duration in seconds

Test

@Slf4j
public class DelayQueueDemo {
    public static void main(String[] args) {
        DelayQueue<SanYouTask> queue = new DelayQueue<>();
        new Thread(() -> {
            while (true) {
                try {
                    SanYouTask task = queue.take();
                    log.info("Got delayed task:{}", task.getTaskContent());
                } catch (Exception e) {}
            }
        }).start();
        log.info("Submitting delayed tasks");
        queue.offer(new SanYouTask("Task 5s", 5L));
        queue.offer(new SanYouTask("Task 3s", 3L));
        queue.offer(new SanYouTask("Task 8s", 8L));
    }
}

The thread takes tasks from the queue; the three tasks are executed after 3, 5, and 8 seconds respectively.

Timer

Timer is another JDK API for delayed execution.

Demo

@Slf4j
public class TimerDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        log.info("Submitting delayed task");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                log.info("Executing delayed task");
            }
        }, 5000);
    }
}

Timer uses a single thread and does not handle runtime exceptions, which can cause the whole timer to crash. Therefore it is not recommended in Alibaba’s coding guidelines.

ScheduledThreadPoolExecutor

Since JDK 1.5, ScheduledThreadPoolExecutor provides a thread‑pool‑based replacement for Timer, solving the single‑thread and exception‑crash problems.

Demo

@Slf4j
public class ScheduledThreadPoolExecutorDemo {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2, new ThreadPoolExecutor.CallerRunsPolicy());
        log.info("Submitting delayed task");
        executor.schedule(() -> log.info("Executing delayed task"), 5, TimeUnit.SECONDS);
    }
}

The executor uses a DelayedWorkQueue internally; tasks are wrapped as ScheduledFutureTask objects and ordered by their delay.

RocketMQ

RocketMQ, an Alibaba‑open‑source message middleware, supports delayed messages with 18 predefined delay levels. The producer sets the delay level when sending a message.

Demo

@RestController
@Slf4j
public class RocketMQDelayTaskController {
    @Resource
    private DefaultMQProducer producer;
    @GetMapping("/rocketmq/add")
    public void addTask(@RequestParam("task") String task) throws Exception {
        Message msg = new Message("sanyouDelayTaskTopic", "TagA", task.getBytes(RemotingHelper.DEFAULT_CHARSET));
        msg.setDelayTimeLevel(2); // 5 seconds
        log.info("Submitting delayed task");
        producer.send(msg);
    }
}

A consumer listening to sanyouDelayTaskTopic receives the delayed message after the configured delay.

RabbitMQ

RabbitMQ can implement delayed tasks using dead‑letter queues. The queue is configured with a TTL (time‑to‑live) and a dead‑letter exchange.

Demo

@RestController
@Slf4j
public class RabbitMQDelayTaskController {
    @Resource
    private RabbitTemplate rabbitTemplate;
    @GetMapping("/rabbitmq/add")
    public void addTask(@RequestParam("task") String task) throws Exception {
        CorrelationData cd = new CorrelationData(UUID.randomUUID().toString());
        log.info("Submitting delayed task");
        rabbitTemplate.convertAndSend("sanyouDirectExchangee", "", task, cd);
    }
}

The message first lands in a TTL‑configured queue; after expiration it is routed to the dead‑letter exchange and finally to the consumer queue.

Listening to Redis Expired Keys

Redis publishes an event on the channel __keyevent@<db>__:expired when a key expires. By storing delayed tasks as keys with the desired TTL and listening to this channel, a consumer can react to the expiration.

Demo

@Component
public class MyRedisKeyExpiredEventListener implements ApplicationListener<RedisKeyExpiredEvent> {
    @Override
    public void onApplicationEvent(RedisKeyExpiredEvent event) {
        byte[] body = event.getSource();
        System.out.println("Got delayed message: " + new String(body));
    }
}

Note that Redis may use lazy or periodic expiration, so a key might not fire the event exactly at the intended time, and the pub/sub model does not persist messages.

Redisson RDelayedQueue

Redisson provides a distributed delayed queue built on Redis data structures.

Demo

@Component
@Slf4j
public class RedissonDelayQueue {
    private RedissonClient redissonClient;
    private RDelayedQueue<String> delayQueue;
    private RBlockingQueue<String> blockingQueue;
    @PostConstruct
    public void init() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        redissonClient = Redisson.create(config);
        blockingQueue = redissonClient.getBlockingQueue("SANYOU");
        delayQueue = redissonClient.getDelayedQueue(blockingQueue);
        new Thread(() -> {
            while (true) {
                try {
                    String task = blockingQueue.take();
                    log.info("Received delayed task:{}", task);
                } catch (Exception e) { e.printStackTrace(); }
            }
        }, "SANYOU-Consumer").start();
    }
    public void offerTask(String task, long seconds) {
        log.info("Adding delayed task:{} with {}s", task, seconds);
        delayQueue.offer(task, seconds, TimeUnit.SECONDS);
    }
}

The implementation uses a sorted set redisson_delay_queue_timeout:SANYOU to store tasks with their execution timestamps, a list SANYOU as the final queue, and a channel to trigger moving ready tasks.

Netty HashedWheelTimer

Netty’s HashedWheelTimer implements a timing wheel. The wheel is divided into slots; each task is hashed to a slot based on its delay. A background thread scans slots and executes due tasks.

Demo

@Slf4j
public class NettyHashedWheelTimerDemo {
    public static void main(String[] args) {
        HashedWheelTimer timer = new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 8);
        timer.start();
        log.info("Submitting delayed task");
        timer.newTimeout(timeout -> log.info("Executing delayed task"), 5, TimeUnit.SECONDS);
    }
}

Hutool SystemTimer

Hutool’s SystemTimer is a wrapper around a timing‑wheel implementation.

Demo

@Slf4j
public class SystemTimerDemo {
    public static void main(String[] args) {
        SystemTimer systemTimer = new SystemTimer();
        systemTimer.start();
        log.info("Submitting delayed task");
        systemTimer.addTask(new TimerTask(() -> log.info("Executing delayed task"), 5000));
    }
}

Quartz

Quartz is a full‑featured job‑scheduling framework. A Job implements the execution logic, a Trigger defines the fire time, and a Scheduler orchestrates them.

Demo

public class SanYouJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap map = context.getJobDetail().getJobDataMap();
        log.info("Got delayed task:{}", map.get("delayTask"));
    }
}

public class QuartzDemo {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler scheduler = sf.getScheduler();
        scheduler.start();
        JobDetail job = JobBuilder.newJob(SanYouJob.class)
                .usingJobData("delayTask", "This is a delayed task")
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .startAt(DateUtil.offsetSecond(new Date(), 5))
                .build();
        log.info("Submitting delayed task");
        scheduler.scheduleJob(job, trigger);
    }
}

Infinite Polling

A simple approach is to keep a thread that continuously scans a list of tasks and executes those whose trigger time has passed.

Demo

@Slf4j
public class PollingTaskDemo {
    private static final List<DelayTask> DELAY_TASK_LIST = new CopyOnWriteArrayList<>();
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                try {
                    for (DelayTask task : DELAY_TASK_LIST) {
                        if (task.getTriggerTime() <= System.currentTimeMillis()) {
                            log.info("Processing delayed task:{}", task.getTaskContent());
                            DELAY_TASK_LIST.remove(task);
                        }
                    }
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (Exception e) {}
            }
        }).start();
        log.info("Submitting delayed task");
        DELAY_TASK_LIST.add(new DelayTask("SanYou Java diary", 5L));
    }
    @Getter @Setter
    public static class DelayTask {
        private final String taskContent;
        private final Long triggerTime;
        public DelayTask(String taskContent, Long delayTime) {
            this.taskContent = taskContent;
            this.triggerTime = System.currentTimeMillis() + delayTime * 1000;
        }
    }
}

This method is easy but inefficient because it scans all tasks each cycle.

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.

javaredisMessageQueueScheduledThreadPoolExecutorDelayQueue
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.