Backend Development 8 min read

Master Dynamic Scheduling in Spring Boot 3.2.5: Custom @Task and Runtime Control

Learn how to dynamically manage Spring Boot 3.2.5 scheduled tasks by creating a custom @Task annotation, recording task metadata, extending ThreadPoolTaskScheduler to track and control tasks, and implementing start/stop APIs, complete with code examples and practical testing.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Dynamic Scheduling in Spring Boot 3.2.5: Custom @Task and Runtime Control

Environment: Spring Boot 3.2.5

1. Introduction

In Spring Boot, the @Scheduled annotation simplifies creating scheduled tasks, but managing them dynamically becomes necessary as applications grow. Spring Boot does not provide built‑in dynamic management, so we need to implement it ourselves.

2. Execution Principle

Spring Boot processes @Scheduled methods via ScheduledAnnotationBeanPostProcessor , wrapping them into Runnable objects. The TaskScheduler interface then executes these tasks, typically using ThreadPoolTaskSchedulerBean backed by ScheduledExecutorService .

<code>public class ScheduledAnnotationBeanPostProcessor {
  public Object postProcessAfterInitialization(Object bean, String beanName) {
    // Find eligible methods
    Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
      (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
        Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
            method, Scheduled.class, Schedules.class);
        return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
      });
    // Process each method, wrapping it into ScheduledMethodRunnable
    annotatedMethods.forEach((method, scheduledAnnotations) ->
      scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
  }
}</code>

The TaskScheduler interface provides methods such as schedule(Runnable, Trigger) , schedule(Runnable, Instant) , and scheduleAtFixedRate(...) , all returning a ScheduledFuture .

<code>public interface TaskScheduler {
  ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
  ScheduledFuture<?> schedule(Runnable task, Instant startTime);
  ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
  // other methods...
}</code>

By default, Spring Boot uses ThreadPoolTaskSchedulerBean , which delegates to ScheduledExecutorService . To manage tasks dynamically we must record each task’s information, allowing us to stop or restart it via the returned Future .

3. Practical Example

We need a meaningful name for each task because @Scheduled does not provide one. We create a custom @Task annotation.

<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Task {
  /** Task name */
  String value() default "";
}</code>

Task metadata is stored in a TaskInfo class.

<code>public class TaskInfo {
  private Runnable task;
  private Instant startTime;
  private Trigger trigger;
  private Duration period;
  private Duration delay;
  private ScheduledFuture<?> future;
}</code>

We extend ThreadPoolTaskScheduler to record task info and provide stop/start operations.

<code>@Component
public class PackTaskScheduler extends ThreadPoolTaskScheduler {
  private static final Map<String, TaskInfo> TASK = new ConcurrentHashMap<>();

  @Override
  public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
    ScheduledFuture<?> schedule = super.schedule(task, trigger);
    if (task instanceof ScheduledMethodRunnable smr) {
      String taskName = parseTask(smr);
      TASK.put(taskName, new TaskInfo(task, null, trigger, null, null, schedule));
    }
    return schedule;
  }

  private String parseTask(ScheduledMethodRunnable smr) {
    Method method = smr.getMethod();
    Task t = method.getAnnotation(Task.class);
    String taskName = method.getName();
    if (t != null && StringUtils.hasLength(t.value())) {
      taskName = t.value();
    }
    return taskName;
  }

  public void stop(String taskName) {
    TaskInfo task = TASK.get(taskName);
    if (task != null) {
      task.getFuture().cancel(true);
    }
  }

  public void start(String taskName) {
    TaskInfo task = TASK.get(taskName);
    if (task != null) {
      if (task.trigger != null) {
        this.schedule(task.getTask(), task.getTrigger());
      }
      if (task.period != null) {
        this.scheduleAtFixedRate(task.getTask(), task.getPeriod());
      }
    }
  }
}</code>

The core functions are:

Override scheduling methods to record task information.

Provide APIs to stop and restart tasks.

Testing the implementation:

<code>@Scheduled(cron = "*/3 * * * * *")
@Task("Test Scheduled Task-01")
public void scheduler() throws Exception {
  System.err.printf("Current time: %s, Thread: %s, Virtual: %b%n",
    new SimpleDateFormat("HH:mm:ss").format(new Date()),
    Thread.currentThread().getName(),
    Thread.currentThread().isVirtual());
}</code>

Controller for stop/start:

<code>private final PackTaskScheduler packTaskScheduler;

public SchedulerController(PackTaskScheduler packTaskScheduler) {
  this.packTaskScheduler = packTaskScheduler;
}

@GetMapping("stop")
public Object stop(String taskName) {
  this.packTaskScheduler.stop(taskName);
  return String.format("Stopped task [%s] successfully", taskName);
}

@GetMapping("/start")
public Object start(String taskName) {
  this.packTaskScheduler.start(taskName);
  return String.format("Started task [%s] successfully", taskName);
}</code>

Calling the above endpoints stops or restarts the specified scheduled task.

Javatask managementSpring BootDynamic Schedulingscheduled tasks
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.