How to Auto‑Cancel Unpaid Orders in Spring Boot Within 30 Minutes

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

Java Architect Handbook
Java Architect Handbook
Java Architect Handbook
How to Auto‑Cancel Unpaid Orders in Spring Boot Within 30 Minutes

Introduction

In e‑commerce and other online‑payment scenarios, it is common to need a mechanism that automatically cancels an order if payment is not completed within a certain time after the order is created. The following sections describe three implementation approaches using Spring Boot.

Solution 1: Scheduled Task

Using Spring Boot’s @Scheduled annotation, a periodic task can scan the database for orders that have been created more than 30 minutes ago and are still unpaid, then cancel them.

@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: Delayed Queue

By leveraging a message‑queue system such as RabbitMQ with delayed‑queue capabilities, the order ID is sent to a delayed queue when the order is created. The message is set to expire after 30 minutes, and a consumer processes the expiration to cancel the order.

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

    public void createOrder(Order order) {
        // Save order to DB
        saveOrderToDB(order);
        // Push order ID to delayed queue (30 minutes)
        rabbitTemplate.convertAndSend(
            "orderDelayExchange",
            "orderDelayKey",
            order.getId(),
            message -> {
                message.getMessageProperties().setDelay(30 * 60 * 1000);
                return message;
            }
        );
    }
}

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

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

Solution 3: Redis Expiration Event

Redis can emit key‑expiration events. When an order is created, a key is stored in Redis with a 30‑minute TTL. By configuring Redis to publish expiration notifications and subscribing to them in Spring Boot, the application can react to the event and cancel the order.

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

    public void createOrder(Order order) {
        // Save order to DB
        saveOrderToDB(order);
        // Store a key with 30‑minute expiration
        redisTemplate.opsForValue().set(
            "order:" + order.getId(),
            order.getId(),
            30,
            TimeUnit.MINUTES
        );
    }

    public void onOrderKeyExpired(String orderId) {
        // Cancel order
        cancelOrder(orderId);
    }
}

To enable Redis key‑space notifications, add the following line to redis.conf:

notify-keyspace-events "Ex"

Then configure a listener in Spring Boot:

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

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

Summary

All three approaches can achieve the requirement of automatically cancelling unpaid orders after 30 minutes. The choice depends on business needs, system load, and existing infrastructure. Scheduled tasks are simple but involve periodic DB scans; delayed queues provide precise timing without extra scans; Redis expiration events are lightweight but require proper Redis configuration.

RedisSpring Bootdelayed queuescheduled taskorder cancellation
Java Architect Handbook
Written by

Java Architect Handbook

Focused on Java interview questions and practical article sharing, covering algorithms, databases, Spring Boot, microservices, high concurrency, JVM, Docker containers, and ELK-related knowledge. Looking forward to progressing together with you.

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.