Backend Development 11 min read

Implementing Automatic Order Cancellation for Timeout Orders in a Monolithic Backend System

This article explains three backend solutions—database polling with scheduled tasks, JDK DelayQueue, and Netty's HashedWheelTimer—for automatically cancelling unpaid orders after a timeout in a monolithic architecture, detailing their implementation steps, pros, cons, and optimization tips with Java code examples.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Automatic Order Cancellation for Timeout Orders in a Monolithic Backend System

Background In e‑commerce or service order systems, orders that exceed the payment timeout are common; an automatic cancellation mechanism is needed to maintain system efficiency and user experience.

Implementation approaches Within a monolithic architecture, three typical solutions are presented: database polling (scheduled task), JDK DelayQueue , and a time‑wheel algorithm using Netty's HashedWheelTimer .

Solution 1: Database polling (scheduled task) A Quartz or Spring scheduler periodically queries unpaid orders, checks the creation time against a configurable timeout, and updates the status to “CANCELED”. Advantages: simple, clear code, low maintenance cost; suitable for small‑to‑medium systems with modest order volume. Disadvantages: consumes server resources, the polling interval must balance performance and timeliness, and it may stress the database under large data volumes. Optimization suggestions include batch processing with pagination, asynchronous execution to avoid blocking the main thread, and adding indexes on payment status and creation time. import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.Duration; import java.time.LocalDateTime; import java.util.List; @Service public class OrderService { @Autowired private OrderInfoMapper orderInfoMapper; public static final Duration ORDER_TIMEOUT = Duration.ofMinutes(30); /** * Scheduled task: runs every minute to cancel timed‑out unpaid orders. */ @Scheduled(fixedRate = 60000) public void cancelUnpaidOrders() { int page = 0; int size = 100; // process 100 orders per page List unpaidOrdersPage; do { unpaidOrdersPage = getUnpaidOrders(page, size); unpaidOrdersPage.forEach(order -> { if (isOrderTimedOut(order)) { order.setOrderStatus(OrderStatus.CANCELED.name()); orderInfoMapper.updateOrderInfo(order); } }); page++; } while (unpaidOrdersPage.size() == size); } private List getUnpaidOrders(int page, int size) { LocalDateTime timeoutThreshold = LocalDateTime.now().minus(ORDER_TIMEOUT); int offset = page * size; return orderInfoMapper.findUnpaidOrders(OrderStatus.UNPAID.name(), timeoutThreshold, offset, size); } private boolean isOrderTimedOut(OrderInfo order) { return LocalDateTime.now().isAfter(order.getCreationTime().plus(ORDER_TIMEOUT)); } public enum OrderStatus { UNPAID, PAID, SHIPPED, COMPLETED, CANCELED, REFUNDED } }

Solution 2: JDK DelayQueue By placing each order into a DelayQueue with a delay equal to the timeout, a consumer thread takes expired tasks and cancels the corresponding orders. Advantages: low latency trigger, simple implementation, high performance for single‑node applications. Disadvantages: data is stored only in memory, so it is lost on server restart; not suitable for clustered environments. Optimizations include persisting delayed tasks to a database or disk and monitoring memory usage to prevent overflow. import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class OrderDelayTask implements Delayed { private final OrderInfo order; private final long startTime; public OrderDelayTask(OrderInfo order, long delayTime) { this.order = order; this.startTime = System.currentTimeMillis() + delayTime; } public OrderInfo getOrder() { return order; } @Override public long getDelay(TimeUnit unit) { return unit.convert(startTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed other) { return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), other.getDelay(TimeUnit.MILLISECONDS)); } } import org.springframework.boot.CommandLineRunner; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.concurrent.DelayQueue; import java.util.concurrent.Executors; @Component public class OrderDelayManager implements CommandLineRunner { @Resource private IOrderInfoService orderInfoService; private DelayQueue delayQueue = new DelayQueue<>(); public static final long ORDER_TIMEOUT = 30L * 60 * 1000; // 30 minutes public void addQueue(OrderInfo order) { delayQueue.put(new OrderDelayTask(order, ORDER_TIMEOUT)); } public void processDelayedOrders() { while (true) { try { OrderDelayTask task = delayQueue.take(); processOrder(task); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void processOrder(OrderDelayTask task) { System.out.println("Start processing timeout task: " + task.getOrder().getOrderNum()); OrderInfo order = task.getOrder(); if (order.getOrderStatus().equals(OrderService.OrderStatus.UNPAID.name())) { order.setOrderStatus(OrderService.OrderStatus.CANCELED.name()); orderInfoService.updateOrderInfo(order); } } @Override public void run(String... args) throws Exception { Executors.newSingleThreadExecutor().execute(new Thread(this::processDelayedOrders)); } }

Solution 3: Time‑wheel algorithm (HashedWheelTimer) Netty's HashedWheelTimer provides a scalable timer wheel that reduces latency for massive delayed tasks. Advantages: more precise and lower latency triggering, moderate implementation complexity. Disadvantages: similar data‑loss risk as in‑memory solutions and no native cluster support. Optimizations include persisting pending tasks to a database or Redis and distributing tasks across multiple timer instances for better scalability. import io.netty.util.HashedWheelTimer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class TimerConfig { @Bean(destroyMethod = "stop") public HashedWheelTimer hashedWheelTimer() { // tick duration 100ms, 512 slots, max delay = 512 * 100ms return new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 512); } } import io.netty.util.HashedWheelTimer; import io.netty.util.TimerTask; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class OrderTimeoutService { private static final long ORDER_TIMEOUT = 30; // minutes @Autowired private HashedWheelTimer timer; @Autowired private OrderInfoMapper orderInfoMapper; public void addDelayTask(OrderInfo order) { TimerTask task = timeout -> { try { OrderInfo current = orderInfoMapper.selectOrderInfoById(order.getId()); if (current != null && current.getOrderStatus().equals(OrderService.OrderStatus.UNPAID.name())) { current.setOrderStatus(OrderService.OrderStatus.CANCELED.name()); orderInfoMapper.updateOrderInfo(current); System.out.println("Order " + order.getOrderNum() + " has been canceled due to timeout."); } } catch (Exception e) { e.printStackTrace(); } }; timer.newTimeout(task, ORDER_TIMEOUT, TimeUnit.MINUTES); } }

Conclusion Choose the appropriate solution based on system scale and latency requirements: small to medium order volumes can use database polling with asynchronous handling; large‑scale, high‑availability systems should consider distributed schedulers or message‑queue based designs, possibly combined with Redis caching; for ultra‑low latency needs, a persisted time‑wheel approach offers the best trade‑off.

JavaBackend DevelopmentSpringDelayQueueorder timeoutHashedWheelTimerautomatic cancellation
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.