How to Auto‑Cancel Unpaid Orders in Spring Boot: 3 Practical Approaches

This guide explains three ways to automatically cancel orders that remain unpaid for 30 minutes in a Spring Boot application, covering scheduled tasks, RabbitMQ delayed queues, and Redis key‑expiration events, with complete code examples and configuration details.

Top Architect
Top Architect
Top Architect
How to Auto‑Cancel Unpaid Orders in Spring Boot: 3 Practical Approaches

Problem

In e‑commerce and other online‑payment scenarios, an order that remains unpaid for more than 30 minutes should be automatically cancelled.

Solution 1 – Scheduled task

Use Spring Boot’s @Scheduled annotation to run a periodic job (e.g., every minute). The job queries the database for unpaid orders, checks the creation timestamp, and calls the order‑cancellation service for orders older than 30 minutes.

@Component
public class OrderCancelSchedule {
    @Autowired
    private OrderService orderService;

    @Scheduled(cron = "0 0/1 * * * ?")
    public void cancelUnpaidOrders() {
        List<Order> unpaidOrders = orderService.getUnpaidOrders();
        unpaidOrders.forEach(order -> {
            if (order.getCreationTime().plusMinutes(30).isBefore(LocalDateTime.now())) {
                orderService.cancelOrder(order.getId());
            }
        });
    }
}

Solution 2 – RabbitMQ delayed queue

When an order is created, push its identifier to a RabbitMQ delayed queue with a 30‑minute TTL. After the delay expires the consumer receives the message and invokes the cancellation logic.

@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void createOrder(Order order) {
        saveOrderToDB(order);
        rabbitTemplate.convertAndSend(
            "orderDelayExchange",
            "orderDelayKey",
            order.getId(),
            message -> {
                message.getMessageProperties().setDelay(30 * 60 * 1000); // 30 min
                return message;
            }
        );
    }
}

@Component
@RabbitListener(queues = "orderDelayQueue")
public class OrderDelayConsumer {
    @Autowired
    private OrderService orderService;

    @RabbitHandler
    public void process(String orderId) {
        orderService.cancelOrder(orderId);
    }
}

Solution 3 – Redis key‑space expiration events

Store a temporary key for each order in Redis with a 30‑minute TTL (e.g., order:{id}). Enable key‑space notifications so Redis publishes an expired event. Spring Boot subscribes to the __keyevent@*__:expired channel, extracts the order id from the key, and cancels the order.

Enable notifications in redis.conf:

notify-keyspace-events "Ex"

Spring configuration and service implementation:

@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    RedisMessageListenerContainer container() {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        container.addMessageListener((Message message, byte[] pattern) -> {
            String expiredKey = message.toString();
            if (expiredKey.startsWith("order:")) {
                String orderId = expiredKey.split(":")[1];
                // orderService.cancelOrder(orderId);
            }
        }, new PatternTopic("__keyevent@*__:expired"));
        return container;
    }
}

@Service
public class OrderService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public void createOrder(Order order) {
        saveOrderToDB(order);
        redisTemplate.opsForValue().set("order:" + order.getId(), order.getId(), 30, TimeUnit.MINUTES);
    }

    public void onOrderKeyExpired(String orderId) {
        cancelOrder(orderId);
    }
}

Choosing a solution

All three approaches satisfy the 30‑minute automatic‑cancellation requirement. Use a scheduled task for simplicity when occasional scanning delay is acceptable. Choose a delayed queue for precise timing at the cost of an additional message‑broker dependency. Prefer Redis expiration events for real‑time cancellation with minimal overhead, provided key‑space notifications are enabled.

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.

RedisSpring BootRabbitMQOrder ManagementScheduled Tasks
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.