DEV Community

DEV-AI
DEV-AI

Posted on

Implementing Task Cancellation in Spring Boot: A Practical Guide

Modern web applications often need to handle long-running operations—data processing, file uploads, report generation, or external API calls. Without proper cancellation mechanisms, these operations can waste resources, frustrate users, and impact system performance. This article explores effective strategies for implementing task cancellation in Spring Boot applications.

Why Task Cancellation Matters

Long-running operations without cancellation support create several problems:

  • Resource waste: Unnecessary computations continue consuming CPU and memory
  • Poor user experience: Users can't stop operations they no longer need
  • System responsiveness: Uncontrolled background tasks can impact application performance

Thread Interruption: The Foundation

Java's primary cancellation mechanism relies on thread interruption—a cooperative system where threads periodically check their interrupt status.

@Service
public class DataProcessingService {

    public void processLargeDataset(List items) {
        for (DataItem item : items) {
            // Check for interruption before processing each item
            if (Thread.currentThread().isInterrupted()) {
                logger.info("Data processing cancelled by user");
                return;
            }

            processItem(item);
        }
    }

    private void processItem(DataItem item) {
        try {
            // Simulate time-consuming work
            Thread.sleep(100);
            // Actual processing logic here
        } catch (InterruptedException e) {
            // Restore interrupt status and exit
            Thread.currentThread().interrupt();
            throw new RuntimeException("Processing interrupted", e);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explicit Control with Boolean Flags

For scenarios requiring more explicit control over cancellation state, volatile boolean flags provide a clear and thread-safe solution:

@Service
public class FileProcessingService {

    private final Map runningTasks = new ConcurrentHashMap<>();

    public void processFiles(String taskId, List files) {
        TaskState state = new TaskState();
        runningTasks.put(taskId, state);

        try {
            for (File file : files) {
                if (state.isCancelled()) {
                    logger.info("File processing cancelled for task: {}", taskId);
                    return;
                }

                processFile(file);
            }
        } finally {
            runningTasks.remove(taskId);
        }
    }

    public boolean cancelTask(String taskId) {
        TaskState state = runningTasks.get(taskId);
        if (state != null) {
            state.cancel();
            return true;
        }
        return false;
    }

    private static class TaskState {
        private volatile boolean cancelled = false;

        public boolean isCancelled() { return cancelled; }
        public void cancel() { cancelled = true; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Enterprise-Grade Task Management

For production applications, combining ExecutorService with Future objects provides robust task management:

@Service
public class AsyncTaskManager {

    private final ExecutorService executorService = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors()
    );
    private final Map> activeTasks = new ConcurrentHashMap<>();

    public String submitTask(Runnable task) {
        String taskId = UUID.randomUUID().toString();

        Future future = executorService.submit(() -> {
            try {
                task.run();
            } finally {
                activeTasks.remove(taskId);
            }
        });

        activeTasks.put(taskId, future);
        return taskId;
    }

    public boolean cancelTask(String taskId) {
        Future future = activeTasks.get(taskId);
        if (future != null) {
            boolean cancelled = future.cancel(true);
            if (cancelled) {
                activeTasks.remove(taskId);
            }
            return cancelled;
        }
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

REST API Integration

Integrating cancellation into your Spring Boot REST API creates a user-friendly experience:

@RestController
@RequestMapping("/api/tasks")
public class TaskController {

    private final AsyncTaskManager taskManager;
    private final DataProcessingService dataService;

    @PostMapping("/process")
    public ResponseEntity startProcessing(@RequestBody ProcessingRequest request) {
        String taskId = taskManager.submitTask(() -> {
            dataService.processLargeDataset(request.getData());
        });

        return ResponseEntity.ok(new TaskResponse(taskId, "Task started"));
    }

    @DeleteMapping("/{taskId}")
    public ResponseEntity cancelTask(@PathVariable String taskId) {
        boolean cancelled = taskManager.cancelTask(taskId);

        return cancelled ? 
            ResponseEntity.ok("Task cancelled") : 
            ResponseEntity.notFound().build();
    }

    @GetMapping("/active")
    public ResponseEntity> getActiveTasks() {
        return ResponseEntity.ok(taskManager.getActiveTasks());
    }
}
Enter fullscreen mode Exit fullscreen mode

Scheduled Task Cancellation

Spring Boot's scheduled tasks support cancellation through ScheduledFuture:

@Service
public class ReportGenerationService {

    private final TaskScheduler taskScheduler;
    private ScheduledFuture reportTask;

    public void startDailyReports() {
        reportTask = taskScheduler.scheduleAtFixedRate(
            this::generateDailyReport,
            Duration.ofHours(24)
        );
    }

    public void stopDailyReports() {
        if (reportTask != null && !reportTask.isDone()) {
            reportTask.cancel(false);
            logger.info("Daily report generation stopped");
        }
    }

    private void generateDailyReport() {
        if (Thread.currentThread().isInterrupted()) {
            return;
        }

        logger.info("Generating daily report...");
        // Report generation logic
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

Strategic Cancellation Checks

Place cancellation checks at natural breakpoints:

public void processItems(List items, TaskState state) {
    for (int i = 0; i > tasks = new ConcurrentHashMap<>();

    @Scheduled(fixedRate = 3600000) // Clean up every hour
    public void cleanupTasks() {
        tasks.entrySet().removeIf(entry -> entry.getValue().get() == null);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Implementing proper task cancellation in Spring Boot requires choosing the right approach for your use case:

  • Thread interruption for simple scenarios
  • Volatile flags for explicit state management
  • ExecutorService with Future for complex task management
  • ScheduledFuture for recurring operations

By implementing these cancellation patterns, you create more responsive and resource-efficient Spring Boot applications that provide excellent user experiences even under heavy computational loads. The key is building cancellation support into your application architecture from the start, making it a first-class concern rather than an afterthought.

Top comments (0)