Spring Boot 4.1.1 • Beginner Teaching Edition

Task Execution and Scheduling

Learn how Spring Boot runs work in background threads and how to execute jobs at scheduled times.
Big Idea: Spring Boot can automatically configure executors and schedulers for you. You can use them for asynchronous work, web request handling, JPA bootstrap, background bean initialization, and scheduled jobs. When needed, you can replace the defaults with your own executor or scheduler.
Source basis: This chapter is based on the current Spring Boot documentation for Task Execution and Scheduling. The current page documents Spring Boot 4.1.1. It covers auto-configured AsyncTaskExecutor, custom executors, @EnableAsync, scheduling, thread-pool configuration, and Java 21+ virtual-thread behavior.

1. First Understand the Problem

Imagine your API receives a request that needs to send an email, generate a report, or perform another slow operation.

If the same request thread does all the work, the user may have to wait. A task executor lets the application hand work to another thread.

HTTP RequestApplicationExecutorWorker ThreadTask

Scheduling is different: instead of asking “run this in another thread now,” you ask “run this at a particular time or repeatedly.”

Task Execution

Run work asynchronously using an executor.

Scheduling

Run work at scheduled times using a scheduler.

Virtual Threads

On Java 21+, Spring Boot can use virtual-thread based executors when enabled.

2. What Spring Boot Configures Automatically

If there is no Executor bean in the application context, Spring Boot auto-configures an AsyncTaskExecutor.

SituationDefault executor
Java 21+ + virtual threads enabledSimpleAsyncTaskExecutor using virtual threads
Normal setupThreadPoolTaskExecutor with sensible defaults
Important: “auto-configured” means you often do not need to manually create an executor just to get started.

3. Where the Auto-Configured AsyncTaskExecutor Is Used

Spring Boot documents several integrations that use the auto-configured executor unless a custom Executor changes the arrangement.

@EnableAsync
Runs asynchronous methods.
Spring MVC
Supports asynchronous request processing.
Spring WebFlux
Provides support for blocking execution.
Spring GraphQL
Handles asynchronous Callable controller return values.
Spring WebSocket
Supports inbound and outbound message channels.
JPA
Can be used as the repository bootstrap executor.
ApplicationContext
Can bootstrap background bean initialization.

4. Simple @EnableAsync Example

First enable asynchronous method execution:

import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Then mark a method with @Async:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class EmailService {

    @Async
    public void sendEmail() {
        // slow work
        System.out.println("Sending email...");
    }
}
Remember: @EnableAsync enables the mechanism; @Async marks the method whose execution should be handled asynchronously.

5. What Happens When You Add Your Own Executor?

By default, when you register a custom Executor bean, Spring Boot's auto-configured AsyncTaskExecutor backs off.

Default ExecutorSpring Boot auto-configures
Custom Executor BeanAuto-configured executor backs off

This is useful when you need different thread names, pool sizes, queue capacity, or separate executors for different workloads.

6. The applicationTaskExecutor Name Matters

Spring MVC, Spring WebFlux, and Spring GraphQL require a bean named applicationTaskExecutor. MVC and WebFlux require that bean to be an AsyncTaskExecutor; Spring GraphQL does not enforce that type requirement.

Spring WebSocket and JPA can use an AsyncTaskExecutor when either a single bean of that type exists or a bean named applicationTaskExecutor exists.

Custom Application Executor

@Configuration(proxyBeanMethods = false)
public class MyTaskExecutorConfiguration {

    @Bean("applicationTaskExecutor")
    SimpleAsyncTaskExecutor applicationTaskExecutor() {
        return new SimpleAsyncTaskExecutor("app-");
    }
}
Interview point: Knowing the special applicationTaskExecutor name is useful when debugging why Spring MVC/WebFlux is not using the executor you expected.

7. Using Separate Executors

You may want one executor for normal @Async work and another for application integrations.

@Configuration(proxyBeanMethods = false)
public class MyTaskExecutorConfiguration {

    @Bean("applicationTaskExecutor")
    SimpleAsyncTaskExecutor applicationTaskExecutor() {
        return new SimpleAsyncTaskExecutor("app-");
    }

    @Bean("taskExecutor")
    ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor =
                new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("async-");
        return executor;
    }
}
BeanTypical role in this setup
applicationTaskExecutorSpring MVC/WebFlux/GraphQL and related integrations
taskExecutorRegular task execution such as @EnableAsync

8. ThreadPoolTaskExecutorBuilder

Spring Boot also provides builders that make it easier to create executors with behavior aligned with Boot's auto-configuration.

@Bean
ThreadPoolTaskExecutor taskExecutor(
        ThreadPoolTaskExecutorBuilder builder) {

    return builder.build();
}

There is also SimpleAsyncTaskExecutorBuilder for creating a SimpleAsyncTaskExecutor.

9. AsyncConfigurer — Another Way to Choose the Async Executor

If a bean named taskExecutor is not suitable, you can define an AsyncConfigurer and return the executor that should handle regular asynchronous tasks.

@Bean
AsyncConfigurer asyncConfigurer(ExecutorService executorService) {
    return new AsyncConfigurer() {
        @Override
        public Executor getAsyncExecutor() {
            return executorService;
        }
    };
}

@Bean
ExecutorService executorService() {
    return Executors.newCachedThreadPool();
}
Memory trick: AsyncConfigurer → getAsyncExecutor() tells Spring which executor to use for regular @Async execution.

10. Keeping Boot's Auto-Configured Executor

You can define a custom Executor while preventing it from becoming the default candidate for auto-configuration decisions.

@Bean(defaultCandidate = false)
@Qualifier("scheduledExecutorService")
ScheduledExecutorService scheduledExecutorService() {
    return Executors.newSingleThreadScheduledExecutor();
}

When autowiring this custom executor elsewhere, use the matching @Qualifier.

11. Force Auto-Configuration

Spring Boot provides spring.task.execution.mode=force when you want the auto-configured AsyncTaskExecutor even when a custom Executor bean exists.

spring.task.execution.mode=force

In YAML:

spring:
  task:
    execution:
      mode: force
Important: In force mode, the auto-configured applicationTaskExecutor is used for the documented integrations even when a custom executor or @Primary executor is present. For regular async tasks, an AsyncConfigurer can still override the executor.

12. Thread Pool Configuration

When ThreadPoolTaskExecutor is auto-configured, Spring Boot uses 8 core threads by default. You can tune the pool through spring.task.execution.

spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s

Equivalent YAML:

spring:
  task:
    execution:
      pool:
        max-size: 16
        queue-capacity: 100
        keep-alive: "10s"
PropertyMeaning
max-sizeMaximum number of threads.
queue-capacityNumber of tasks that can wait in the queue.
keep-aliveHow long extra idle threads are kept before being reclaimed.
Example behavior: with core size 8, queue capacity 100, and max size 16, tasks can queue while the pool is at its core size. When the bounded queue fills, the pool can grow toward 16 threads.

13. Scheduling: Run Work at a Certain Time

A scheduler is used when a task needs to execute according to a schedule. Spring Boot can auto-configure a scheduler when scheduled task execution is needed, for example with @EnableScheduling.

import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class MyApplication {
}

A scheduled method can then be declared using Spring's scheduling annotations:

@Scheduled(fixedRate = 5000)
public void refreshData() {
    System.out.println("Refreshing...");
}
Task execution vs scheduling: @Async answers “run this asynchronously”; @Scheduled answers “run this according to a schedule.”

14. Default Scheduler

SituationScheduler
Virtual threads enabledSimpleAsyncTaskScheduler using virtual threads
Virtual threads not enabledThreadPoolTaskScheduler

The normal ThreadPoolTaskScheduler uses one thread by default.

15. Configure the Scheduler

spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2

YAML:

spring:
  task:
    scheduling:
      thread-name-prefix: "scheduling-"
      pool:
        size: 2
PropertyPurpose
thread-name-prefixMakes scheduler thread names easier to identify in logs.
pool.sizeControls the scheduler pool size.

16. Virtual Threads

Spring Boot supports virtual-thread-based task execution and scheduling when running Java 21+ and enabling:

spring.threads.virtual.enabled=true

Async execution

Uses SimpleAsyncTaskExecutor with virtual threads.

Scheduling

Uses SimpleAsyncTaskScheduler with virtual threads.

Pool properties

The virtual-thread scheduler ignores pooling-related properties.

Do not mix up: virtual threads and a traditional thread pool solve concurrency differently. When virtual-thread support is enabled, the relevant Boot auto-configuration switches to the simple virtual-thread-based implementations documented above.

17. Builder Summary

Spring Boot exposes builder beans when custom executors or schedulers need to be created.

BuilderBuilds
ThreadPoolTaskExecutorBuilderThreadPoolTaskExecutor
SimpleAsyncTaskExecutorBuilderSimpleAsyncTaskExecutor
ThreadPoolTaskSchedulerBuilderThreadPoolTaskScheduler
SimpleAsyncTaskSchedulerBuilderSimpleAsyncTaskScheduler

18. How to Choose the Right Option

Just need @Async?

Start with Boot's auto-configured executor.

Need custom pool sizing?

Configure spring.task.execution.pool.* or create a custom executor.

Need scheduled jobs?

Enable scheduling and configure spring.task.scheduling.* if needed.

Need separate workloads?

Define multiple executors and make their roles explicit with names/qualifiers.

Using Java 21+?

Consider virtual threads through spring.threads.virtual.enabled=true.

19. Common Mistakes

  • Creating many executor beans without understanding which integration selects which bean.
  • Forgetting the special applicationTaskExecutor name for MVC/WebFlux/GraphQL integrations.
  • Assuming @Async works without enabling async processing.
  • Confusing asynchronous execution with scheduled execution.
  • Increasing thread-pool sizes without considering workload and resource limits.
  • Expecting scheduler pool properties to control a virtual-thread-based scheduler.
  • Injecting multiple executors without using clear bean names or qualifiers.

20. Best Practices

  • Start with Spring Boot defaults unless you have a reason to customize.
  • Give different executors clear names when multiple workloads exist.
  • Use @Qualifier when injecting a specific executor.
  • Use bounded queues and sensible maximum pool sizes for controlled workloads.
  • Give scheduler threads recognizable names for troubleshooting.
  • Measure workload behavior before changing concurrency settings.
  • Understand which Spring integration is consuming your executor.
  • When using virtual threads, understand that traditional pool tuning properties may no longer apply to the virtual-thread implementation.

21. Interview Questions

Q1. What is AsyncTaskExecutor?

It is Spring's asynchronous task-execution abstraction used by Spring Boot's auto-configuration for several asynchronous integrations.

Q2. What happens if I define my own Executor bean?

By default, Spring Boot's auto-configured AsyncTaskExecutor backs off and the custom executor can become the executor used for regular task execution.

Q3. Why is applicationTaskExecutor important?

Spring MVC, WebFlux, and GraphQL look for this bean name for their task execution integration.

Q4. What is the difference between @Async and @Scheduled?

@Async is about asynchronous execution; @Scheduled is about executing according to a schedule.

Q5. What is spring.task.execution.mode=force?

It asks Spring Boot to auto-configure its AsyncTaskExecutor even when a custom Executor bean is present.

Q6. What changes when virtual threads are enabled?

On Java 21+, Boot uses virtual-thread-based executor/scheduler implementations for the relevant auto-configuration.

22. Practice Exercises

  1. Create a Spring Boot application with @EnableAsync and an @Async service method.
  2. Configure a custom applicationTaskExecutor and print thread names.
  3. Create a separate taskExecutor and compare its thread names with the application executor.
  4. Configure max-size=16 and queue-capacity=100. Observe how the executor behaves under load.
  5. Enable scheduling and create a method that runs every few seconds.
  6. Change the scheduler thread name prefix to scheduling- and inspect the logs.
  7. Run on Java 21+ and test spring.threads.virtual.enabled=true.
  8. Explain why an executor bean may be selected for one Spring integration but not another.

23. Quick Cheat Sheet

NeedKey API / Property
Enable async@EnableAsync
Mark async method@Async
Application integration executorapplicationTaskExecutor
Regular executor nametaskExecutor
Async overrideAsyncConfigurer
Force Boot executorspring.task.execution.mode=force
Execution pool settingsspring.task.execution.pool.*
Enable scheduling@EnableScheduling
Scheduled method@Scheduled
Scheduling settingsspring.task.scheduling.*
Virtual threadsspring.threads.virtual.enabled=true

24. Memory Map

@AsyncAsyncTaskExecutorWorker Thread
@ScheduledSchedulerScheduled Execution
Custom ExecutorBean naming / qualifiersSpecific integration
Java 21++virtual.enabled=trueVirtual-thread implementations

Final Takeaway

Spring Boot handles a lot of task execution and scheduling configuration automatically. Learn the difference between an executor and a scheduler, understand the special applicationTaskExecutor bean, know how custom executors affect auto-configuration, and remember that Java 21+ virtual threads can change which implementation Boot uses.


Official reference: Spring Boot Reference Documentation — Task Execution and Scheduling. This teaching edition intentionally explains the documented concepts in simpler language and adds learning structure, examples, exercises, and interview-focused revision.