or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

admin-jmx.mdansi-support.mdaot-native-image.mdapplication-info.mdavailability.mdbootstrap.mdbootstrapping.mdbuilder.mdcloud-platform.mdconfiguration-annotations.mdconfiguration-data.mdconfiguration-properties.mdconversion.mddiagnostics.mdenvironment-property-sources.mdindex.mdjson-support.mdlifecycle-events.mdlogging.mdorigin-tracking.mdresource-loading.mdretry-support.mdssl-tls.mdstartup-metrics.mdsupport.mdsystem-utilities.mdtask-execution.mdthreading.mdutilities.mdvalidation.mdweb-support.md
tile.json

task-execution.mddocs/

Task Execution

Package: org.springframework.boot.task Module: org.springframework.boot:spring-boot Since: 3.2.0

Spring Boot provides builder classes for configuring task executors and schedulers with sensible defaults and convenient fluent APIs. These builders simplify the creation of thread pool executors and schedulers for asynchronous task execution in Spring applications.

Quick Reference

ComponentPurposeThread SafetyKey Features
ThreadPoolTaskExecutorBuilderConfigure ThreadPoolTaskExecutorImmutable builder, thread-safe resultQueue-based thread pool with configurable sizing
ThreadPoolTaskSchedulerBuilderConfigure ThreadPoolTaskSchedulerImmutable builder, thread-safe resultScheduled task execution with fixed thread pool
SimpleAsyncTaskExecutorBuilderConfigure SimpleAsyncTaskExecutorImmutable builder, thread-safe resultUnbounded executor, supports virtual threads (Java 21+)
SimpleAsyncTaskSchedulerBuilderConfigure SimpleAsyncTaskSchedulerImmutable builder, thread-safe resultScheduled tasks with unbounded threading
TaskDecoratorWrap task executionImplementation-dependentContext propagation, monitoring, security

Core Components

ThreadPoolTaskExecutorBuilder

Builder for creating and configuring ThreadPoolTaskExecutor instances with common settings.

Thread Safety: This builder is immutable and thread-safe. Each configuration method returns a new builder instance. The resulting ThreadPoolTaskExecutor is thread-safe for concurrent task submission.

package org.springframework.boot.task;

import java.time.Duration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * Builder that can be used to configure and create a ThreadPoolTaskExecutor.
 * Provides a fluent API for configuring thread pool parameters, shutdown behavior,
 * and task decoration.
 *
 * Thread Safety: Immutable builder - each method returns a new instance.
 * The built ThreadPoolTaskExecutor is thread-safe for concurrent use.
 *
 * @since 3.2.0
 */
public class ThreadPoolTaskExecutorBuilder {

    /**
     * Create a new ThreadPoolTaskExecutorBuilder instance with default settings.
     * Default settings:
     * - corePoolSize: 1
     * - maxPoolSize: Integer.MAX_VALUE
     * - queueCapacity: Integer.MAX_VALUE (unbounded)
     * - keepAlive: 60 seconds
     * - allowCoreThreadTimeOut: false
     * - awaitTermination: false
     */
    public ThreadPoolTaskExecutorBuilder() {
        // Creates builder with default settings
    }

    /**
     * Set the capacity of the queue. When the queue is full and all core threads
     * are busy, new threads are created up to maxPoolSize.
     *
     * @param queueCapacity the queue capacity to set (must be >= 0)
     * @return a new builder instance with updated queue capacity
     * @throws IllegalArgumentException if queueCapacity < 0
     */
    public ThreadPoolTaskExecutorBuilder queueCapacity(int queueCapacity) {
        // Returns new builder instance
    }

    /**
     * Set the core number of threads. These threads are kept alive even when idle
     * (unless allowCoreThreadTimeOut is true).
     *
     * @param corePoolSize the core pool size to set (must be >= 0)
     * @return a new builder instance with updated core pool size
     * @throws IllegalArgumentException if corePoolSize < 0
     */
    public ThreadPoolTaskExecutorBuilder corePoolSize(int corePoolSize) {
        // Returns new builder instance
    }

    /**
     * Set the maximum allowed number of threads. When queue is full, threads
     * are created up to this limit.
     *
     * @param maxPoolSize the max pool size to set (must be >= corePoolSize)
     * @return a new builder instance with updated max pool size
     * @throws IllegalArgumentException if maxPoolSize < corePoolSize
     */
    public ThreadPoolTaskExecutorBuilder maxPoolSize(int maxPoolSize) {
        // Returns new builder instance
    }

    /**
     * Set whether core threads are allowed to time out. If true, core threads
     * use keepAlive timeout when waiting for work.
     *
     * @param allowCoreThreadTimeOut if core threads are allowed to time out
     * @return a new builder instance with updated timeout setting
     */
    public ThreadPoolTaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
        // Returns new builder instance
    }

    /**
     * Set the time limit for which threads may remain idle before being terminated.
     * Applies to threads beyond corePoolSize, or to all threads if
     * allowCoreThreadTimeOut is true.
     *
     * @param keepAlive the keep alive duration (must not be null, typically 30-300 seconds)
     * @return a new builder instance with updated keep alive duration
     * @throws IllegalArgumentException if keepAlive is null
     */
    public ThreadPoolTaskExecutorBuilder keepAlive(Duration keepAlive) {
        // Returns new builder instance
    }

    /**
     * Set whether to accept further tasks after the application context close phase
     * has begun. When false, tasks submitted after shutdown starts are rejected.
     *
     * @param acceptTasksAfterContextClose whether to accept tasks after context close
     * @return a new builder instance with updated setting
     * @since 3.2.0
     */
    public ThreadPoolTaskExecutorBuilder acceptTasksAfterContextClose(boolean acceptTasksAfterContextClose) {
        // Returns new builder instance
    }

    /**
     * Set whether the executor should wait for scheduled tasks to complete on shutdown.
     * When true, executor waits up to awaitTerminationPeriod for tasks to finish.
     *
     * @param awaitTermination whether to await termination
     * @return a new builder instance with updated await setting
     */
    public ThreadPoolTaskExecutorBuilder awaitTermination(boolean awaitTermination) {
        // Returns new builder instance
    }

    /**
     * Set the maximum time the executor should wait for remaining tasks to complete
     * on shutdown. Only effective if awaitTermination is true.
     *
     * @param awaitTerminationPeriod the await termination period (must not be null)
     * @return a new builder instance with updated await period
     * @throws IllegalArgumentException if awaitTerminationPeriod is null
     */
    public ThreadPoolTaskExecutorBuilder awaitTerminationPeriod(Duration awaitTerminationPeriod) {
        // Returns new builder instance
    }

    /**
     * Set the prefix to use for the names of newly created threads.
     * Thread names follow pattern: prefix + incrementing number.
     *
     * @param threadNamePrefix the thread name prefix to set (must not be null)
     * @return a new builder instance with updated thread name prefix
     * @throws IllegalArgumentException if threadNamePrefix is null
     */
    public ThreadPoolTaskExecutorBuilder threadNamePrefix(String threadNamePrefix) {
        // Returns new builder instance
    }

    /**
     * Set the TaskDecorator to use. The decorator wraps each Runnable before
     * execution, useful for context propagation, logging, or monitoring.
     *
     * @param taskDecorator the task decorator to use (may be null to clear)
     * @return a new builder instance with updated task decorator
     */
    public ThreadPoolTaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) {
        // Returns new builder instance
    }

    /**
     * Set the customizers that should be applied to the ThreadPoolTaskExecutor.
     * Replaces any previously configured customizers.
     *
     * @param customizers the customizers to set (varargs, may be empty)
     * @return a new builder instance with updated customizers
     */
    public ThreadPoolTaskExecutorBuilder customizers(ThreadPoolTaskExecutorCustomizer... customizers) {
        // Returns new builder instance
    }

    /**
     * Set the customizers that should be applied to the ThreadPoolTaskExecutor.
     * Replaces any previously configured customizers.
     *
     * @param customizers the customizers to set (iterable, may be empty)
     * @return a new builder instance with updated customizers
     */
    public ThreadPoolTaskExecutorBuilder customizers(Iterable<? extends ThreadPoolTaskExecutorCustomizer> customizers) {
        // Returns new builder instance
    }

    /**
     * Add customizers that should be applied to the ThreadPoolTaskExecutor.
     * Adds to any previously configured customizers.
     *
     * @param customizers the customizers to add (varargs, may be empty)
     * @return a new builder instance with additional customizers
     */
    public ThreadPoolTaskExecutorBuilder additionalCustomizers(ThreadPoolTaskExecutorCustomizer... customizers) {
        // Returns new builder instance
    }

    /**
     * Add customizers that should be applied to the ThreadPoolTaskExecutor.
     * Adds to any previously configured customizers.
     *
     * @param customizers the customizers to add (iterable, may be empty)
     * @return a new builder instance with additional customizers
     */
    public ThreadPoolTaskExecutorBuilder additionalCustomizers(Iterable<? extends ThreadPoolTaskExecutorCustomizer> customizers) {
        // Returns new builder instance
    }

    /**
     * Build a new ThreadPoolTaskExecutor instance and initialize it.
     * Calls initialize() on the executor before returning.
     *
     * @return a configured and initialized ThreadPoolTaskExecutor instance
     * @throws IllegalStateException if configuration is invalid
     */
    public ThreadPoolTaskExecutor build() {
        // Creates, configures, and initializes executor
    }

    /**
     * Build a new ThreadPoolTaskExecutor instance and configure it.
     * Does NOT call initialize() - caller is responsible for initialization.
     *
     * @param <T> the type of task executor
     * @param taskExecutor the task executor to configure (must not be null)
     * @return the passed in task executor (configured but not initialized)
     * @throws IllegalArgumentException if taskExecutor is null
     */
    public <T extends ThreadPoolTaskExecutor> T configure(T taskExecutor) {
        // Configures existing executor instance
    }
}

ThreadPoolTaskExecutorCustomizer

Callback interface for customizing ThreadPoolTaskExecutor instances.

Thread Safety: Implementations must be thread-safe if shared across threads. Typically invoked during bean creation (single-threaded).

package org.springframework.boot.task;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * Callback interface that can be used to customize a ThreadPoolTaskExecutor.
 * Applied after builder configuration but before initialization.
 *
 * Thread Safety: Implementations should be thread-safe if shared.
 * Typically invoked only during bean creation phase.
 *
 * @since 3.2.0
 */
@FunctionalInterface
public interface ThreadPoolTaskExecutorCustomizer {

    /**
     * Callback to customize a ThreadPoolTaskExecutor instance.
     * Called after builder configuration, before initialize().
     *
     * @param taskExecutor the task executor to customize (never null)
     */
    void customize(ThreadPoolTaskExecutor taskExecutor);
}

ThreadPoolTaskSchedulerBuilder

Builder for creating and configuring ThreadPoolTaskScheduler instances.

Thread Safety: This builder is immutable and thread-safe. The resulting ThreadPoolTaskScheduler is thread-safe for concurrent task scheduling.

package org.springframework.boot.task;

import java.time.Duration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
 * Builder that can be used to configure and create a ThreadPoolTaskScheduler.
 * Provides a fluent API for configuring scheduled task execution.
 *
 * Thread Safety: Immutable builder - each method returns a new instance.
 * The built ThreadPoolTaskScheduler is thread-safe for concurrent use.
 *
 * @since 3.2.0
 */
public class ThreadPoolTaskSchedulerBuilder {

    /**
     * Create a new ThreadPoolTaskSchedulerBuilder instance with default settings.
     * Default settings:
     * - poolSize: 1
     * - awaitTermination: false
     * - threadNamePrefix: null (uses default)
     */
    public ThreadPoolTaskSchedulerBuilder() {
        // Creates builder with default settings
    }

    /**
     * Set the pool size. Determines the number of threads available for
     * executing scheduled tasks concurrently.
     *
     * @param poolSize the pool size to set (must be >= 1)
     * @return a new builder instance with updated pool size
     * @throws IllegalArgumentException if poolSize < 1
     */
    public ThreadPoolTaskSchedulerBuilder poolSize(int poolSize) {
        // Returns new builder instance
    }

    /**
     * Set whether the executor should wait for scheduled tasks to complete on shutdown.
     * When true, scheduler waits up to awaitTerminationPeriod for tasks to finish.
     *
     * @param awaitTermination whether to await termination
     * @return a new builder instance with updated await setting
     */
    public ThreadPoolTaskSchedulerBuilder awaitTermination(boolean awaitTermination) {
        // Returns new builder instance
    }

    /**
     * Set the maximum time the executor should wait for remaining tasks to complete
     * on shutdown. Only effective if awaitTermination is true.
     *
     * @param awaitTerminationPeriod the await termination period (must not be null)
     * @return a new builder instance with updated await period
     * @throws IllegalArgumentException if awaitTerminationPeriod is null
     */
    public ThreadPoolTaskSchedulerBuilder awaitTerminationPeriod(Duration awaitTerminationPeriod) {
        // Returns new builder instance
    }

    /**
     * Set the prefix to use for the names of newly created threads.
     * Thread names follow pattern: prefix + incrementing number.
     *
     * @param threadNamePrefix the thread name prefix to set (must not be null)
     * @return a new builder instance with updated thread name prefix
     * @throws IllegalArgumentException if threadNamePrefix is null
     */
    public ThreadPoolTaskSchedulerBuilder threadNamePrefix(String threadNamePrefix) {
        // Returns new builder instance
    }

    /**
     * Set the TaskDecorator to be applied to the ThreadPoolTaskScheduler.
     * The decorator wraps each Runnable before execution.
     *
     * @param taskDecorator the task decorator to set (may be null to clear)
     * @return a new builder instance with updated task decorator
     * @since 3.5.0
     */
    public ThreadPoolTaskSchedulerBuilder taskDecorator(TaskDecorator taskDecorator) {
        // Returns new builder instance
    }

    /**
     * Set the customizers that should be applied to the ThreadPoolTaskScheduler.
     * Replaces any previously configured customizers.
     *
     * @param customizers the customizers to set (varargs, may be empty)
     * @return a new builder instance with updated customizers
     */
    public ThreadPoolTaskSchedulerBuilder customizers(ThreadPoolTaskSchedulerCustomizer... customizers) {
        // Returns new builder instance
    }

    /**
     * Set the customizers that should be applied to the ThreadPoolTaskScheduler.
     * Replaces any previously configured customizers.
     *
     * @param customizers the customizers to set (iterable, may be empty)
     * @return a new builder instance with updated customizers
     */
    public ThreadPoolTaskSchedulerBuilder customizers(Iterable<? extends ThreadPoolTaskSchedulerCustomizer> customizers) {
        // Returns new builder instance
    }

    /**
     * Add customizers that should be applied to the ThreadPoolTaskScheduler.
     * Adds to any previously configured customizers.
     *
     * @param customizers the customizers to add (varargs, may be empty)
     * @return a new builder instance with additional customizers
     */
    public ThreadPoolTaskSchedulerBuilder additionalCustomizers(ThreadPoolTaskSchedulerCustomizer... customizers) {
        // Returns new builder instance
    }

    /**
     * Add customizers that should be applied to the ThreadPoolTaskScheduler.
     * Adds to any previously configured customizers.
     *
     * @param customizers the customizers to add (iterable, may be empty)
     * @return a new builder instance with additional customizers
     */
    public ThreadPoolTaskSchedulerBuilder additionalCustomizers(Iterable<? extends ThreadPoolTaskSchedulerCustomizer> customizers) {
        // Returns new builder instance
    }

    /**
     * Build a new ThreadPoolTaskScheduler instance and initialize it.
     * Calls initialize() on the scheduler before returning.
     *
     * @return a configured and initialized ThreadPoolTaskScheduler instance
     * @throws IllegalStateException if configuration is invalid
     */
    public ThreadPoolTaskScheduler build() {
        // Creates, configures, and initializes scheduler
    }

    /**
     * Build a new ThreadPoolTaskScheduler instance and configure it.
     * Does NOT call initialize() - caller is responsible for initialization.
     *
     * @param <T> the type of task scheduler
     * @param taskScheduler the task scheduler to configure (must not be null)
     * @return the passed in task scheduler (configured but not initialized)
     * @throws IllegalArgumentException if taskScheduler is null
     */
    public <T extends ThreadPoolTaskScheduler> T configure(T taskScheduler) {
        // Configures existing scheduler instance
    }
}

ThreadPoolTaskSchedulerCustomizer

Callback interface for customizing ThreadPoolTaskScheduler instances.

Thread Safety: Implementations must be thread-safe if shared across threads. Typically invoked during bean creation (single-threaded).

package org.springframework.boot.task;

import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
 * Callback interface that can be used to customize a ThreadPoolTaskScheduler.
 * Applied after builder configuration but before initialization.
 *
 * Thread Safety: Implementations should be thread-safe if shared.
 * Typically invoked only during bean creation phase.
 *
 * @since 3.2.0
 */
@FunctionalInterface
public interface ThreadPoolTaskSchedulerCustomizer {

    /**
     * Callback to customize a ThreadPoolTaskScheduler instance.
     * Called after builder configuration, before initialize().
     *
     * @param taskScheduler the task scheduler to customize (never null)
     */
    void customize(ThreadPoolTaskScheduler taskScheduler);
}

SimpleAsyncTaskExecutorBuilder

Builder for creating and configuring SimpleAsyncTaskExecutor instances.

Thread Safety: This builder is immutable and thread-safe. The resulting SimpleAsyncTaskExecutor is thread-safe for concurrent task submission.

package org.springframework.boot.task;

import java.time.Duration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskDecorator;

/**
 * Builder that can be used to configure and create a SimpleAsyncTaskExecutor.
 * SimpleAsyncTaskExecutor creates a new thread for each task (or virtual thread
 * when configured).
 *
 * Thread Safety: Immutable builder - each method returns a new instance.
 * The built SimpleAsyncTaskExecutor is thread-safe for concurrent use.
 *
 * @since 3.2.0
 */
public class SimpleAsyncTaskExecutorBuilder {

    /**
     * Create a new SimpleAsyncTaskExecutorBuilder instance with default settings.
     * Default settings:
     * - virtualThreads: false
     * - concurrencyLimit: no limit
     * - rejectTasksWhenLimitReached: false
     * - cancelRemainingTasksOnClose: false
     */
    public SimpleAsyncTaskExecutorBuilder() {
        // Creates builder with default settings
    }

    /**
     * Set the prefix to use for the names of newly created threads.
     * Thread names follow pattern: prefix + incrementing number.
     *
     * @param threadNamePrefix the thread name prefix to set (must not be null)
     * @return a new builder instance with updated thread name prefix
     * @throws IllegalArgumentException if threadNamePrefix is null
     */
    public SimpleAsyncTaskExecutorBuilder threadNamePrefix(String threadNamePrefix) {
        // Returns new builder instance
    }

    /**
     * Set whether to create virtual threads. Virtual threads (Java 21+) provide
     * lightweight concurrency with minimal memory overhead.
     *
     * IMPORTANT: Requires Java 21 or later. Throws exception on earlier versions.
     *
     * @param virtualThreads whether to use virtual threads
     * @return a new builder instance with updated virtual threads setting
     * @throws UnsupportedOperationException if Java version < 21
     */
    public SimpleAsyncTaskExecutorBuilder virtualThreads(boolean virtualThreads) {
        // Returns new builder instance
    }

    /**
     * Set whether to cancel remaining tasks on close. When true, tasks in progress
     * are interrupted on executor shutdown.
     *
     * @param cancelRemainingTasksOnClose whether to cancel remaining tasks on close
     * @return a new builder instance with updated cancel setting
     * @since 4.0.0
     */
    public SimpleAsyncTaskExecutorBuilder cancelRemainingTasksOnClose(boolean cancelRemainingTasksOnClose) {
        // Returns new builder instance
    }

    /**
     * Set whether to reject tasks when the concurrency limit has been reached.
     * When true, throws TaskRejectedException if limit exceeded.
     * When false, blocks until capacity becomes available.
     *
     * @param rejectTasksWhenLimitReached whether to reject tasks when the concurrency limit has been reached
     * @return a new builder instance with updated reject setting
     * @since 3.5.0
     */
    public SimpleAsyncTaskExecutorBuilder rejectTasksWhenLimitReached(boolean rejectTasksWhenLimitReached) {
        // Returns new builder instance
    }

    /**
     * Set the concurrency limit. Limits the number of concurrent task executions.
     * Use SimpleAsyncTaskExecutor.NO_CONCURRENCY for unlimited concurrency.
     *
     * @param concurrencyLimit the concurrency limit (must be > 0 or NO_CONCURRENCY)
     * @return a new builder instance with updated concurrency limit
     * @throws IllegalArgumentException if concurrencyLimit <= 0 and not NO_CONCURRENCY
     */
    public SimpleAsyncTaskExecutorBuilder concurrencyLimit(int concurrencyLimit) {
        // Returns new builder instance
    }

    /**
     * Set the TaskDecorator to use. The decorator wraps each Runnable before
     * execution, useful for context propagation, logging, or monitoring.
     *
     * @param taskDecorator the task decorator to use (may be null to clear)
     * @return a new builder instance with updated task decorator
     */
    public SimpleAsyncTaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) {
        // Returns new builder instance
    }

    /**
     * Set the TaskTerminationTimeout to use. Maximum time to wait for tasks
     * to complete during shutdown.
     *
     * @param taskTerminationTimeout the task termination timeout (must not be null)
     * @return a new builder instance with updated termination timeout
     * @throws IllegalArgumentException if taskTerminationTimeout is null
     */
    public SimpleAsyncTaskExecutorBuilder taskTerminationTimeout(Duration taskTerminationTimeout) {
        // Returns new builder instance
    }

    /**
     * Set the customizers that should be applied to the SimpleAsyncTaskExecutor.
     * Replaces any previously configured customizers.
     *
     * @param customizers the customizers to set (varargs, may be empty)
     * @return a new builder instance with updated customizers
     */
    public SimpleAsyncTaskExecutorBuilder customizers(SimpleAsyncTaskExecutorCustomizer... customizers) {
        // Returns new builder instance
    }

    /**
     * Set the customizers that should be applied to the SimpleAsyncTaskExecutor.
     * Replaces any previously configured customizers.
     *
     * @param customizers the customizers to set (iterable, may be empty)
     * @return a new builder instance with updated customizers
     */
    public SimpleAsyncTaskExecutorBuilder customizers(Iterable<? extends SimpleAsyncTaskExecutorCustomizer> customizers) {
        // Returns new builder instance
    }

    /**
     * Add customizers that should be applied to the SimpleAsyncTaskExecutor.
     * Adds to any previously configured customizers.
     *
     * @param customizers the customizers to add (varargs, may be empty)
     * @return a new builder instance with additional customizers
     */
    public SimpleAsyncTaskExecutorBuilder additionalCustomizers(SimpleAsyncTaskExecutorCustomizer... customizers) {
        // Returns new builder instance
    }

    /**
     * Add customizers that should be applied to the SimpleAsyncTaskExecutor.
     * Adds to any previously configured customizers.
     *
     * @param customizers the customizers to add (iterable, may be empty)
     * @return a new builder instance with additional customizers
     */
    public SimpleAsyncTaskExecutorBuilder additionalCustomizers(Iterable<? extends SimpleAsyncTaskExecutorCustomizer> customizers) {
        // Returns new builder instance
    }

    /**
     * Build a new SimpleAsyncTaskExecutor instance.
     * Uses default SimpleAsyncTaskExecutor class.
     *
     * @return a configured SimpleAsyncTaskExecutor instance
     * @throws IllegalStateException if configuration is invalid
     */
    public SimpleAsyncTaskExecutor build() {
        // Creates and configures executor
    }

    /**
     * Build a new SimpleAsyncTaskExecutor instance of the specified type.
     * Allows using custom subclasses of SimpleAsyncTaskExecutor.
     *
     * @param <T> the type of task executor
     * @param taskExecutorClass the template type to create (must not be null, must have no-arg constructor)
     * @return a configured SimpleAsyncTaskExecutor instance
     * @throws IllegalArgumentException if taskExecutorClass is null or lacks no-arg constructor
     * @throws IllegalStateException if configuration is invalid
     */
    public <T extends SimpleAsyncTaskExecutor> T build(Class<T> taskExecutorClass) {
        // Creates and configures executor of specified type
    }

    /**
     * Configure the provided SimpleAsyncTaskExecutor instance using this builder.
     *
     * @param <T> the type of task executor
     * @param taskExecutor the task executor to configure (must not be null)
     * @return the passed in task executor (configured)
     * @throws IllegalArgumentException if taskExecutor is null
     */
    public <T extends SimpleAsyncTaskExecutor> T configure(T taskExecutor) {
        // Configures existing executor instance
    }
}

SimpleAsyncTaskExecutorCustomizer

Callback interface for customizing SimpleAsyncTaskExecutor instances.

Thread Safety: Implementations must be thread-safe if shared across threads. Typically invoked during bean creation (single-threaded).

package org.springframework.boot.task;

import org.springframework.core.task.SimpleAsyncTaskExecutor;

/**
 * Callback interface that can be used to customize a SimpleAsyncTaskExecutor.
 * Applied after builder configuration.
 *
 * Thread Safety: Implementations should be thread-safe if shared.
 * Typically invoked only during bean creation phase.
 *
 * @since 3.2.0
 */
@FunctionalInterface
public interface SimpleAsyncTaskExecutorCustomizer {

    /**
     * Callback to customize a SimpleAsyncTaskExecutor instance.
     * Called after builder configuration.
     *
     * @param taskExecutor the task executor to customize (never null)
     */
    void customize(SimpleAsyncTaskExecutor taskExecutor);
}

SimpleAsyncTaskSchedulerBuilder

Builder for creating and configuring SimpleAsyncTaskScheduler instances.

Thread Safety: This builder is immutable and thread-safe. The resulting SimpleAsyncTaskScheduler is thread-safe for concurrent task scheduling.

package org.springframework.boot.task;

import java.time.Duration;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;

/**
 * Builder that can be used to configure and create a SimpleAsyncTaskScheduler.
 * SimpleAsyncTaskScheduler creates a new thread for each scheduled task execution.
 *
 * Thread Safety: Immutable builder - each method returns a new instance.
 * The built SimpleAsyncTaskScheduler is thread-safe for concurrent use.
 *
 * @since 3.2.0
 */
public class SimpleAsyncTaskSchedulerBuilder {

    /**
     * Create a new SimpleAsyncTaskSchedulerBuilder instance with default settings.
     * Default settings:
     * - virtualThreads: false
     * - concurrencyLimit: no limit
     */
    public SimpleAsyncTaskSchedulerBuilder() {
        // Creates builder with default settings
    }

    /**
     * Set the prefix to use for the names of newly created threads.
     * Thread names follow pattern: prefix + incrementing number.
     *
     * @param threadNamePrefix the thread name prefix to set (must not be null)
     * @return a new builder instance with updated thread name prefix
     * @throws IllegalArgumentException if threadNamePrefix is null
     */
    public SimpleAsyncTaskSchedulerBuilder threadNamePrefix(String threadNamePrefix) {
        // Returns new builder instance
    }

    /**
     * Set the concurrency limit. Limits the number of concurrent task executions.
     * Use SimpleAsyncTaskScheduler.NO_CONCURRENCY for unlimited concurrency.
     *
     * @param concurrencyLimit the concurrency limit (must be > 0 or NO_CONCURRENCY)
     * @return a new builder instance with updated concurrency limit
     * @throws IllegalArgumentException if concurrencyLimit <= 0 and not NO_CONCURRENCY
     */
    public SimpleAsyncTaskSchedulerBuilder concurrencyLimit(int concurrencyLimit) {
        // Returns new builder instance
    }

    /**
     * Set whether to create virtual threads. Virtual threads (Java 21+) provide
     * lightweight concurrency with minimal memory overhead.
     *
     * IMPORTANT: Requires Java 21 or later. Throws exception on earlier versions.
     *
     * @param virtualThreads whether to use virtual threads
     * @return a new builder instance with updated virtual threads setting
     * @throws UnsupportedOperationException if Java version < 21
     */
    public SimpleAsyncTaskSchedulerBuilder virtualThreads(boolean virtualThreads) {
        // Returns new builder instance
    }

    /**
     * Set the TaskTerminationTimeout to use. Maximum time to wait for tasks
     * to complete during shutdown.
     *
     * @param taskTerminationTimeout the task termination timeout (must not be null)
     * @return a new builder instance with updated termination timeout
     * @throws IllegalArgumentException if taskTerminationTimeout is null
     */
    public SimpleAsyncTaskSchedulerBuilder taskTerminationTimeout(Duration taskTerminationTimeout) {
        // Returns new builder instance
    }

    /**
     * Set the customizers that should be applied to the SimpleAsyncTaskScheduler.
     * Replaces any previously configured customizers.
     *
     * @param customizers the customizers to set (varargs, may be empty)
     * @return a new builder instance with updated customizers
     */
    public SimpleAsyncTaskSchedulerBuilder customizers(SimpleAsyncTaskSchedulerCustomizer... customizers) {
        // Returns new builder instance
    }

    /**
     * Set the customizers that should be applied to the SimpleAsyncTaskScheduler.
     * Replaces any previously configured customizers.
     *
     * @param customizers the customizers to set (iterable, may be empty)
     * @return a new builder instance with updated customizers
     */
    public SimpleAsyncTaskSchedulerBuilder customizers(Iterable<? extends SimpleAsyncTaskSchedulerCustomizer> customizers) {
        // Returns new builder instance
    }

    /**
     * Add customizers that should be applied to the SimpleAsyncTaskScheduler.
     * Adds to any previously configured customizers.
     *
     * @param customizers the customizers to add (varargs, may be empty)
     * @return a new builder instance with additional customizers
     */
    public SimpleAsyncTaskSchedulerBuilder additionalCustomizers(SimpleAsyncTaskSchedulerCustomizer... customizers) {
        // Returns new builder instance
    }

    /**
     * Add customizers that should be applied to the SimpleAsyncTaskScheduler.
     * Adds to any previously configured customizers.
     *
     * @param customizers the customizers to add (iterable, may be empty)
     * @return a new builder instance with additional customizers
     */
    public SimpleAsyncTaskSchedulerBuilder additionalCustomizers(Iterable<? extends SimpleAsyncTaskSchedulerCustomizer> customizers) {
        // Returns new builder instance
    }

    /**
     * Build a new SimpleAsyncTaskScheduler instance.
     *
     * @return a configured SimpleAsyncTaskScheduler instance
     * @throws IllegalStateException if configuration is invalid
     */
    public SimpleAsyncTaskScheduler build() {
        // Creates and configures scheduler
    }

    /**
     * Configure the provided SimpleAsyncTaskScheduler instance using this builder.
     *
     * @param <T> the type of task scheduler
     * @param taskScheduler the task scheduler to configure (must not be null)
     * @return the passed in task scheduler (configured)
     * @throws IllegalArgumentException if taskScheduler is null
     */
    public <T extends SimpleAsyncTaskScheduler> T configure(T taskScheduler) {
        // Configures existing scheduler instance
    }
}

SimpleAsyncTaskSchedulerCustomizer

Callback interface for customizing SimpleAsyncTaskScheduler instances.

Thread Safety: Implementations must be thread-safe if shared across threads. Typically invoked during bean creation (single-threaded).

package org.springframework.boot.task;

import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;

/**
 * Callback interface that can be used to customize a SimpleAsyncTaskScheduler.
 * Applied after builder configuration.
 *
 * Thread Safety: Implementations should be thread-safe if shared.
 * Typically invoked only during bean creation phase.
 *
 * @since 3.2.0
 */
@FunctionalInterface
public interface SimpleAsyncTaskSchedulerCustomizer {

    /**
     * Callback to customize a SimpleAsyncTaskScheduler instance.
     * Called after builder configuration.
     *
     * @param taskScheduler the task scheduler to customize (never null)
     */
    void customize(SimpleAsyncTaskScheduler taskScheduler);
}

Complete Usage Examples

Basic ThreadPoolTaskExecutor

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.time.Duration;
import java.util.concurrent.Future;

public class BasicExecutorExample {

    public static void main(String[] args) throws Exception {
        // Create a configured task executor
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorBuilder()
            .corePoolSize(4)
            .maxPoolSize(8)
            .queueCapacity(100)
            .threadNamePrefix("my-app-")
            .keepAlive(Duration.ofSeconds(60))
            .awaitTermination(true)
            .awaitTerminationPeriod(Duration.ofSeconds(30))
            .build();

        try {
            // Execute a simple task
            executor.execute(() -> {
                System.out.println("Task executed by: " + Thread.currentThread().getName());
            });

            // Submit a task that returns a result
            Future<String> future = executor.submit(() -> {
                Thread.sleep(1000);
                return "Task completed";
            });

            String result = future.get();
            System.out.println("Result: " + result);

        } finally {
            // Shutdown executor (waits for tasks to complete)
            executor.shutdown();
        }
    }
}

ThreadPoolTaskScheduler with Fixed Rate

import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ScheduledFuture;

public class SchedulerExample {

    public static void main(String[] args) throws Exception {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskSchedulerBuilder()
            .poolSize(5)
            .threadNamePrefix("scheduler-")
            .awaitTermination(true)
            .awaitTerminationPeriod(Duration.ofSeconds(30))
            .build();

        try {
            // Schedule a task at fixed rate
            ScheduledFuture<?> fixedRate = scheduler.scheduleAtFixedRate(() -> {
                System.out.println("Fixed rate task at " + Instant.now());
            }, Duration.ofSeconds(10));

            // Schedule a task with fixed delay
            ScheduledFuture<?> fixedDelay = scheduler.scheduleWithFixedDelay(() -> {
                System.out.println("Fixed delay task at " + Instant.now());
            }, Duration.ofSeconds(5));

            // Schedule a one-time task
            ScheduledFuture<?> oneTime = scheduler.schedule(() -> {
                System.out.println("One-time task executed");
            }, Instant.now().plusSeconds(15));

            // Wait for some time
            Thread.sleep(30000);

            // Cancel scheduled tasks
            fixedRate.cancel(false);
            fixedDelay.cancel(false);

        } finally {
            scheduler.shutdown();
        }
    }
}

Virtual Threads with SimpleAsyncTaskExecutor (Java 21+)

import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class VirtualThreadsExample {

    public static void main(String[] args) throws Exception {
        // Create executor with virtual threads (Java 21+)
        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutorBuilder()
            .threadNamePrefix("virtual-")
            .virtualThreads(true)
            .concurrencyLimit(1000)
            .build();

        try {
            CountDownLatch latch = new CountDownLatch(1000);

            // Submit 1000 concurrent tasks (would be expensive with platform threads)
            for (int i = 0; i < 1000; i++) {
                final int taskId = i;
                executor.execute(() -> {
                    try {
                        // Simulate I/O-bound work
                        Thread.sleep(100);
                        System.out.println("Task " + taskId + " on " +
                            Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    } finally {
                        latch.countDown();
                    }
                });
            }

            // Wait for all tasks to complete
            boolean completed = latch.await(10, TimeUnit.SECONDS);
            System.out.println("All tasks completed: " + completed);

        } finally {
            executor.close();
        }
    }
}

Error Handling

Task Rejection Handling

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.core.task.TaskRejectedException;
import java.time.Duration;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;

public class RejectionHandlingExample {

    public static ThreadPoolTaskExecutor createExecutorWithRejectionPolicy() {
        return new ThreadPoolTaskExecutorBuilder()
            .corePoolSize(2)
            .maxPoolSize(4)
            .queueCapacity(10)  // Small queue to trigger rejection
            .threadNamePrefix("bounded-")
            .build();
    }

    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = createExecutorWithRejectionPolicy();

        // Configure rejection policy via customizer
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();

        try {
            // Submit many tasks
            for (int i = 0; i < 100; i++) {
                final int taskId = i;
                try {
                    executor.execute(() -> {
                        try {
                            Thread.sleep(1000);
                            System.out.println("Task " + taskId + " completed");
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    });
                } catch (TaskRejectedException e) {
                    // Handle rejection
                    System.err.println("Task " + taskId + " rejected: " + e.getMessage());

                    // Could implement retry logic, fallback, or circuit breaker here
                }
            }
        } finally {
            executor.shutdown();
        }
    }
}

Graceful Shutdown with Timeout Monitoring

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

public class GracefulShutdownExample {

    public static void shutdownGracefully(ThreadPoolTaskExecutor executor) {
        try {
            System.out.println("Initiating shutdown...");

            // Initiate shutdown (no new tasks accepted)
            executor.shutdown();

            // Wait for tasks to complete
            boolean terminated = executor.getThreadPoolExecutor()
                .awaitTermination(30, TimeUnit.SECONDS);

            if (!terminated) {
                System.err.println("Tasks did not complete in time, forcing shutdown");
                executor.getThreadPoolExecutor().shutdownNow();

                // Wait for forced shutdown
                if (!executor.getThreadPoolExecutor()
                    .awaitTermination(5, TimeUnit.SECONDS)) {
                    System.err.println("Some tasks did not terminate");
                }
            } else {
                System.out.println("Shutdown completed gracefully");
            }

        } catch (InterruptedException e) {
            System.err.println("Shutdown interrupted");
            executor.getThreadPoolExecutor().shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorBuilder()
            .corePoolSize(5)
            .awaitTermination(true)
            .awaitTerminationPeriod(Duration.ofSeconds(30))
            .build();

        // Submit some long-running tasks
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.execute(() -> {
                try {
                    System.out.println("Task " + taskId + " started");
                    Thread.sleep(5000);
                    System.out.println("Task " + taskId + " finished");
                } catch (InterruptedException e) {
                    System.out.println("Task " + taskId + " interrupted");
                    Thread.currentThread().interrupt();
                }
            });
        }

        // Shutdown after delay
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        shutdownGracefully(executor);
    }
}

Common Patterns

Spring Boot Auto-Configuration Integration

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration
@EnableAsync
public class TaskExecutorConfig {

    /**
     * Injected ThreadPoolTaskExecutorBuilder is pre-configured with
     * properties from application.properties:
     *
     * spring.task.execution.pool.core-size=10
     * spring.task.execution.pool.max-size=20
     * spring.task.execution.pool.queue-capacity=500
     * spring.task.execution.thread-name-prefix=app-task-
     */
    @Bean
    public ThreadPoolTaskExecutor applicationTaskExecutor(
            ThreadPoolTaskExecutorBuilder builder) {
        return builder
            .corePoolSize(10)
            .maxPoolSize(20)
            .queueCapacity(500)
            .threadNamePrefix("app-task-")
            .build();
    }

    /**
     * Separate executor for CPU-intensive tasks
     */
    @Bean(name = "cpuTaskExecutor")
    public ThreadPoolTaskExecutor cpuTaskExecutor(
            ThreadPoolTaskExecutorBuilder builder) {
        int processors = Runtime.getRuntime().availableProcessors();
        return builder
            .corePoolSize(processors)
            .maxPoolSize(processors * 2)
            .queueCapacity(100)
            .threadNamePrefix("cpu-task-")
            .build();
    }

    /**
     * Separate executor for I/O-intensive tasks
     */
    @Bean(name = "ioTaskExecutor")
    public ThreadPoolTaskExecutor ioTaskExecutor(
            ThreadPoolTaskExecutorBuilder builder) {
        return builder
            .corePoolSize(20)
            .maxPoolSize(50)
            .queueCapacity(1000)
            .threadNamePrefix("io-task-")
            .build();
    }
}

Task Decorator for Context Propagation

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.slf4j.MDC;
import java.util.Map;

public class ContextPropagationExample {

    /**
     * TaskDecorator that propagates SecurityContext to async tasks
     */
    public static class SecurityContextTaskDecorator implements TaskDecorator {

        @Override
        public Runnable decorate(Runnable runnable) {
            // Capture security context from submitting thread
            SecurityContext context = SecurityContextHolder.getContext();

            return () -> {
                try {
                    // Set context in executing thread
                    SecurityContextHolder.setContext(context);
                    runnable.run();
                } finally {
                    // Clear context after execution
                    SecurityContextHolder.clearContext();
                }
            };
        }
    }

    /**
     * TaskDecorator that propagates MDC (Mapped Diagnostic Context) to async tasks
     */
    public static class MdcTaskDecorator implements TaskDecorator {

        @Override
        public Runnable decorate(Runnable runnable) {
            // Capture MDC from submitting thread
            Map<String, String> contextMap = MDC.getCopyOfContextMap();

            return () -> {
                try {
                    // Set MDC in executing thread
                    if (contextMap != null) {
                        MDC.setContextMap(contextMap);
                    }
                    runnable.run();
                } finally {
                    // Clear MDC after execution
                    MDC.clear();
                }
            };
        }
    }

    /**
     * Composite TaskDecorator that chains multiple decorators
     */
    public static class CompositeTaskDecorator implements TaskDecorator {

        private final TaskDecorator[] decorators;

        public CompositeTaskDecorator(TaskDecorator... decorators) {
            this.decorators = decorators;
        }

        @Override
        public Runnable decorate(Runnable runnable) {
            Runnable decorated = runnable;
            for (TaskDecorator decorator : decorators) {
                decorated = decorator.decorate(decorated);
            }
            return decorated;
        }
    }

    public static ThreadPoolTaskExecutor createExecutorWithContextPropagation() {
        TaskDecorator compositeDecorator = new CompositeTaskDecorator(
            new SecurityContextTaskDecorator(),
            new MdcTaskDecorator()
        );

        return new ThreadPoolTaskExecutorBuilder()
            .corePoolSize(5)
            .maxPoolSize(10)
            .threadNamePrefix("context-aware-")
            .taskDecorator(compositeDecorator)
            .build();
    }
}

Custom Executor Customizer

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ExecutorCustomizerConfig {

    /**
     * Global customizer applied to all ThreadPoolTaskExecutor instances
     */
    @Bean
    public ThreadPoolTaskExecutorCustomizer threadPoolTaskExecutorCustomizer() {
        return executor -> {
            // Set rejection policy to CallerRunsPolicy
            executor.setRejectedExecutionHandler(
                new ThreadPoolExecutor.CallerRunsPolicy()
            );

            // Wait for tasks to complete on shutdown
            executor.setWaitForTasksToCompleteOnShutdown(true);

            // Set thread priority
            executor.setThreadPriority(Thread.NORM_PRIORITY);

            // Set daemon threads
            executor.setDaemon(false);
        };
    }

    /**
     * Custom rejection handler with logging
     */
    public static class LoggingRejectedExecutionHandler
            implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.err.println("Task rejected: " + r.toString());
            System.err.println("Pool stats - Active: " + executor.getActiveCount() +
                ", Queue: " + executor.getQueue().size() +
                ", Completed: " + executor.getCompletedTaskCount());

            // Could log to monitoring system, trigger alerts, etc.

            // Optionally use CallerRunsPolicy behavior
            if (!executor.isShutdown()) {
                r.run();
            }
        }
    }

    @Bean
    public ThreadPoolTaskExecutor customizedExecutor(
            ThreadPoolTaskExecutorBuilder builder) {
        return builder
            .corePoolSize(5)
            .maxPoolSize(10)
            .queueCapacity(25)
            .customizers(executor -> {
                executor.setRejectedExecutionHandler(
                    new LoggingRejectedExecutionHandler()
                );
            })
            .build();
    }
}

Monitoring and Metrics

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.time.Duration;

public class MonitoringExample {

    public static class ExecutorMetrics {
        private final ThreadPoolTaskExecutor executor;

        public ExecutorMetrics(ThreadPoolTaskExecutor executor) {
            this.executor = executor;
        }

        public void printMetrics() {
            ThreadPoolExecutor threadPool = executor.getThreadPoolExecutor();

            System.out.println("=== Executor Metrics ===");
            System.out.println("Pool Size: " + threadPool.getPoolSize());
            System.out.println("Core Pool Size: " + threadPool.getCorePoolSize());
            System.out.println("Max Pool Size: " + threadPool.getMaximumPoolSize());
            System.out.println("Active Threads: " + threadPool.getActiveCount());
            System.out.println("Queue Size: " + threadPool.getQueue().size());
            System.out.println("Queue Remaining Capacity: " +
                threadPool.getQueue().remainingCapacity());
            System.out.println("Completed Tasks: " + threadPool.getCompletedTaskCount());
            System.out.println("Total Tasks: " + threadPool.getTaskCount());
            System.out.println("Largest Pool Size: " +
                threadPool.getLargestPoolSize());
        }

        public boolean isHealthy() {
            ThreadPoolExecutor threadPool = executor.getThreadPoolExecutor();

            // Check if queue is near capacity
            int queueSize = threadPool.getQueue().size();
            int queueCapacity = queueSize +
                threadPool.getQueue().remainingCapacity();
            double queueUtilization = (double) queueSize / queueCapacity;

            if (queueUtilization > 0.9) {
                System.err.println("WARNING: Queue utilization at " +
                    (queueUtilization * 100) + "%");
                return false;
            }

            // Check if pool is near max size
            if (threadPool.getPoolSize() >= threadPool.getMaximumPoolSize() * 0.9) {
                System.err.println("WARNING: Thread pool near capacity");
                return false;
            }

            return true;
        }
    }

    public static void main(String[] args) throws Exception {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorBuilder()
            .corePoolSize(5)
            .maxPoolSize(10)
            .queueCapacity(50)
            .threadNamePrefix("monitored-")
            .build();

        ExecutorMetrics metrics = new ExecutorMetrics(executor);

        try {
            // Submit tasks
            for (int i = 0; i < 20; i++) {
                final int taskId = i;
                executor.execute(() -> {
                    try {
                        Thread.sleep(2000);
                        System.out.println("Task " + taskId + " completed");
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            }

            // Monitor periodically
            for (int i = 0; i < 10; i++) {
                Thread.sleep(1000);
                metrics.printMetrics();

                if (!metrics.isHealthy()) {
                    System.err.println("Executor health check failed!");
                }
            }

        } finally {
            executor.shutdown();
        }
    }
}

Cron-based Scheduling with ThreadPoolTaskScheduler

import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import java.time.Duration;
import java.time.Instant;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;

public class CronSchedulingExample {

    public static void main(String[] args) throws Exception {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskSchedulerBuilder()
            .poolSize(5)
            .threadNamePrefix("cron-scheduler-")
            .awaitTermination(true)
            .awaitTerminationPeriod(Duration.ofSeconds(30))
            .build();

        try {
            // Schedule task with cron expression (every weekday at 9 AM)
            CronTrigger weekdayMorning = new CronTrigger(
                "0 0 9 * * MON-FRI",
                TimeZone.getDefault()
            );

            ScheduledFuture<?> dailyTask = scheduler.schedule(() -> {
                System.out.println("Daily morning task at " + Instant.now());
                // Perform daily maintenance, reports, etc.
            }, weekdayMorning);

            // Schedule task with cron expression (every hour)
            CronTrigger hourly = new CronTrigger("0 0 * * * *");

            ScheduledFuture<?> hourlyTask = scheduler.schedule(() -> {
                System.out.println("Hourly task at " + Instant.now());
                // Perform hourly checks, cleanups, etc.
            }, hourly);

            // Schedule task with cron expression (every 5 minutes)
            CronTrigger everyFiveMinutes = new CronTrigger("0 */5 * * * *");

            ScheduledFuture<?> frequentTask = scheduler.schedule(() -> {
                System.out.println("5-minute task at " + Instant.now());
                // Perform frequent monitoring, health checks, etc.
            }, everyFiveMinutes);

            // Keep running for demonstration
            System.out.println("Scheduler started. Press Ctrl+C to stop.");
            Thread.sleep(3600000); // Run for 1 hour

        } finally {
            scheduler.shutdown();
        }
    }
}

Production-Ready Patterns

High-Availability Configuration

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.time.Duration;

@Configuration
public class ProductionExecutorConfig {

    /**
     * Production-grade executor configuration with:
     * - Appropriate pool sizing based on workload
     * - Graceful shutdown handling
     * - Queue management
     * - Rejection policy
     * - Monitoring hooks
     */
    @Bean
    @Primary
    public ThreadPoolTaskExecutor productionTaskExecutor(
            ThreadPoolTaskExecutorBuilder builder) {

        int processors = Runtime.getRuntime().availableProcessors();

        return builder
            // Pool sizing: N to 2N for I/O-bound tasks
            .corePoolSize(processors * 2)
            .maxPoolSize(processors * 4)

            // Bounded queue to prevent memory exhaustion
            .queueCapacity(1000)

            // Allow core threads to timeout for dynamic scaling
            .allowCoreThreadTimeOut(true)
            .keepAlive(Duration.ofMinutes(2))

            // Graceful shutdown
            .awaitTermination(true)
            .awaitTerminationPeriod(Duration.ofSeconds(60))
            .acceptTasksAfterContextClose(false)

            // Thread naming for debugging
            .threadNamePrefix("prod-executor-")

            // Apply production customizations
            .customizers(executor -> {
                // Use CallerRunsPolicy for backpressure
                executor.setRejectedExecutionHandler(
                    new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()
                );

                // Ensure clean shutdown
                executor.setWaitForTasksToCompleteOnShutdown(true);

                // Normal priority threads
                executor.setThreadPriority(Thread.NORM_PRIORITY);
            })
            .build();
    }
}

Kubernetes/Cloud-Native Configuration

import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import java.time.Duration;

@Configuration
@Profile("kubernetes")
public class KubernetesExecutorConfig {

    /**
     * Cloud-native executor configuration that:
     * - Respects container resource limits
     * - Handles graceful pod termination (SIGTERM)
     * - Scales based on available resources
     */
    @Bean
    public ThreadPoolTaskExecutor cloudNativeExecutor(
            ThreadPoolTaskExecutorBuilder builder) {

        // Read from container limits if available
        int cpuLimit = getCpuLimit();
        int memoryLimitMb = getMemoryLimitMb();

        // Calculate pool sizes based on resources
        int corePoolSize = Math.max(2, cpuLimit);
        int maxPoolSize = Math.max(4, cpuLimit * 2);
        int queueCapacity = calculateQueueCapacity(memoryLimitMb);

        return builder
            .corePoolSize(corePoolSize)
            .maxPoolSize(maxPoolSize)
            .queueCapacity(queueCapacity)

            // Critical: Allow time for Kubernetes graceful shutdown
            // K8s default terminationGracePeriodSeconds is 30s
            .awaitTermination(true)
            .awaitTerminationPeriod(Duration.ofSeconds(25))

            // Stop accepting new tasks on shutdown signal
            .acceptTasksAfterContextClose(false)

            .threadNamePrefix("k8s-executor-")

            .customizers(executor -> {
                // Fail fast on overload
                executor.setRejectedExecutionHandler(
                    new java.util.concurrent.ThreadPoolExecutor.AbortPolicy()
                );
            })
            .build();
    }

    private int getCpuLimit() {
        // In Kubernetes, could read from:
        // - Environment variable (e.g., KUBERNETES_CPU_LIMIT)
        // - /sys/fs/cgroup/cpu/cpu.cfs_quota_us
        // - OperatingSystemMXBean
        String cpuLimit = System.getenv("KUBERNETES_CPU_LIMIT");
        return cpuLimit != null ? Integer.parseInt(cpuLimit) :
            Runtime.getRuntime().availableProcessors();
    }

    private int getMemoryLimitMb() {
        // In Kubernetes, could read from:
        // - Environment variable (e.g., KUBERNETES_MEMORY_LIMIT)
        // - /sys/fs/cgroup/memory/memory.limit_in_bytes
        String memLimit = System.getenv("KUBERNETES_MEMORY_LIMIT_MB");
        return memLimit != null ? Integer.parseInt(memLimit) : 512;
    }

    private int calculateQueueCapacity(int memoryLimitMb) {
        // Allocate ~10% of memory for task queue
        // Assuming ~1KB per queued task
        return Math.min(10000, (memoryLimitMb * 1024 * 10) / 100 / 1);
    }
}

Best Practices

1. Choose the Right Executor Type

// ThreadPoolTaskExecutor - For bounded, queue-based execution
// Use when: You need controlled resource usage and backpressure
ThreadPoolTaskExecutor boundedExecutor = new ThreadPoolTaskExecutorBuilder()
    .corePoolSize(10)
    .maxPoolSize(20)
    .queueCapacity(100)
    .build();

// SimpleAsyncTaskExecutor with platform threads - For unbounded execution
// Use when: Task volume is predictable and moderate
SimpleAsyncTaskExecutor platformThreadExecutor = new SimpleAsyncTaskExecutorBuilder()
    .concurrencyLimit(50)
    .build();

// SimpleAsyncTaskExecutor with virtual threads - For high-concurrency I/O
// Use when: Many concurrent I/O-bound tasks (Java 21+)
SimpleAsyncTaskExecutor virtualThreadExecutor = new SimpleAsyncTaskExecutorBuilder()
    .virtualThreads(true)
    .concurrencyLimit(10000)
    .build();

2. Configure Graceful Shutdown

// Always configure graceful shutdown in production
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorBuilder()
    .corePoolSize(5)
    .awaitTermination(true)  // Wait for tasks to complete
    .awaitTerminationPeriod(Duration.ofSeconds(30))  // Max wait time
    .acceptTasksAfterContextClose(false)  // Reject new tasks on shutdown
    .build();

3. Use Appropriate Pool Sizing

// CPU-bound tasks: N to N+1 threads (N = number of cores)
int cpuBoundPoolSize = Runtime.getRuntime().availableProcessors();

// I/O-bound tasks: N to 2N threads
int ioBoundPoolSize = Runtime.getRuntime().availableProcessors() * 2;

// Mixed workload: Separate executors for CPU and I/O tasks

4. Implement Context Propagation

// Always propagate security context, MDC, and other thread-local data
TaskDecorator contextDecorator = new CompositeTaskDecorator(
    new SecurityContextTaskDecorator(),
    new MdcTaskDecorator(),
    new CustomContextTaskDecorator()
);

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorBuilder()
    .taskDecorator(contextDecorator)
    .build();

5. Configure Rejection Policies

// Choose appropriate rejection policy for your use case:

// 1. AbortPolicy (default) - Throw RejectedExecutionException
//    Use when: Failures should be visible and handled explicitly

// 2. CallerRunsPolicy - Execute in caller's thread (backpressure)
//    Use when: Must process all tasks, can slow down submission

// 3. DiscardPolicy - Silently discard task
//    Use when: Some task loss is acceptable

// 4. DiscardOldestPolicy - Discard oldest queued task
//    Use when: Newer tasks are more important

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorBuilder()
    .customizers(e -> e.setRejectedExecutionHandler(
        new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()
    ))
    .build();

6. Monitor Executor Health

// Regularly monitor executor metrics in production
public void monitorExecutor(ThreadPoolTaskExecutor executor) {
    ThreadPoolExecutor pool = executor.getThreadPoolExecutor();

    // Log or emit metrics
    logMetric("executor.pool.size", pool.getPoolSize());
    logMetric("executor.active.threads", pool.getActiveCount());
    logMetric("executor.queue.size", pool.getQueue().size());
    logMetric("executor.completed.tasks", pool.getCompletedTaskCount());

    // Alert on unhealthy conditions
    double queueUtilization = calculateQueueUtilization(pool);
    if (queueUtilization > 0.9) {
        alertHighQueueUtilization();
    }
}

7. Use Virtual Threads for I/O-Heavy Workloads (Java 21+)

// Virtual threads excel at I/O-bound workloads with high concurrency
// Do NOT use for CPU-intensive tasks
SimpleAsyncTaskExecutor virtualExecutor = new SimpleAsyncTaskExecutorBuilder()
    .virtualThreads(true)
    .concurrencyLimit(10000)  // Can handle many more concurrent tasks
    .build();

// Perfect for: HTTP clients, database connections, file I/O
// Avoid for: Heavy computation, blocking synchronized code

8. Handle Interruption Properly

executor.execute(() -> {
    try {
        // Task logic
        performWork();
    } catch (InterruptedException e) {
        // Restore interrupted status
        Thread.currentThread().interrupt();

        // Cleanup and exit
        cleanup();

        // Log interruption
        logger.warn("Task interrupted", e);
    }
});

Integration with Spring Features

@Async Method Execution

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class AsyncService {

    /**
     * Uses default task executor (injected ThreadPoolTaskExecutor)
     */
    @Async
    public void processAsync(String data) {
        // Executes asynchronously
        System.out.println("Processing: " + data);
    }

    /**
     * Uses specific executor by bean name
     */
    @Async("ioTaskExecutor")
    public CompletableFuture<String> fetchDataAsync(String url) {
        // Executes on ioTaskExecutor
        String result = fetchFromUrl(url);
        return CompletableFuture.completedFuture(result);
    }

    private String fetchFromUrl(String url) {
        // HTTP call
        return "data";
    }
}

@Scheduled Method Execution

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling
public class ScheduledTasks {

    /**
     * Uses default task scheduler (injected ThreadPoolTaskScheduler)
     */
    @Scheduled(fixedRate = 60000)  // Every 60 seconds
    public void performPeriodicTask() {
        System.out.println("Periodic task executed");
    }

    @Scheduled(cron = "0 0 * * * *")  // Every hour
    public void performHourlyTask() {
        System.out.println("Hourly task executed");
    }
}

Notes

Thread Safety

  • All builders are immutable - each configuration method returns a new instance
  • Built executors and schedulers are thread-safe for concurrent task submission
  • Customizers should be thread-safe if shared across multiple builder instances

Resource Management

  • Always call shutdown() or use try-with-resources for proper cleanup
  • Configure awaitTermination and awaitTerminationPeriod for graceful shutdown
  • Monitor queue sizes and pool utilization to prevent resource exhaustion

Performance Considerations

  • ThreadPoolTaskExecutor: Bounded resources, good for controlled throughput
  • SimpleAsyncTaskExecutor: Unbounded threading, suitable for bursty workloads
  • Virtual threads (Java 21+): Excellent for high-concurrency I/O, minimal overhead
  • Separate executors for CPU-bound vs I/O-bound tasks for optimal performance

Spring Boot Integration

  • Builders are auto-configured in Spring Boot applications
  • Properties can be set via application.properties or application.yml
  • Inject builders into configuration classes for customization
  • Multiple executors can be defined with different names using @Bean

Common Pitfalls

  • Not configuring graceful shutdown (leads to task loss)
  • Using unbounded queues (can cause OutOfMemoryError)
  • Wrong pool sizing (too small = poor throughput, too large = resource waste)
  • Not handling task rejection (leads to uncaught exceptions)
  • Using virtual threads for CPU-intensive tasks (no benefit)
  • Not propagating thread-local context (breaks security, logging)