or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

aot-native-support.mdauthentication-core.mdauthentication-events.mdauthentication-management.mdauthentication-tokens.mdauthorities.mdauthorization.mdcompromised-password.mdconcurrent-async.mddao-authentication.mdexpression-access-control.mdindex.mdjaas-authentication.mdjackson-serialization.mdmethod-security.mdobservation-metrics.mdone-time-tokens.mdprovisioning.mdsecurity-context.mdsession-management.mduser-details.md
tile.json

concurrent-async.mddocs/

Concurrent and Async Support

Spring Security Core provides comprehensive support for propagating security context across thread boundaries in concurrent and asynchronous execution scenarios. This ensures that security context is available in background threads, executor services, and scheduled tasks.

Key Information for Agents

Core Capabilities:

  • DelegatingSecurityContextExecutor - Executor that propagates security context
  • DelegatingSecurityContextExecutorService - ExecutorService with context propagation
  • DelegatingSecurityContextTaskExecutor - TaskExecutor with context propagation
  • DelegatingSecurityContextAsyncTaskExecutor - AsyncTaskExecutor with context propagation
  • DelegatingSecurityContextTaskScheduler - TaskScheduler with context propagation
  • SecurityContextHolderStrategy - Strategy for storing security context (ThreadLocal, InheritableThreadLocal, Global, Listening)

Key Interfaces and Classes:

  • DelegatingSecurityContextExecutor - Wraps Executor, propagates context
  • DelegatingSecurityContextExecutorService - Wraps ExecutorService, propagates context
  • DelegatingSecurityContextTaskExecutor - Wraps TaskExecutor, propagates context
  • DelegatingSecurityContextAsyncTaskExecutor - Wraps AsyncTaskExecutor, propagates context
  • DelegatingSecurityContextTaskScheduler - Wraps TaskScheduler, propagates context
  • SecurityContextHolderStrategy - Strategy interface for context storage
  • ThreadLocalSecurityContextHolderStrategy - ThreadLocal storage (default)
  • InheritableThreadLocalSecurityContextHolderStrategy - InheritableThreadLocal storage
  • GlobalSecurityContextHolderStrategy - Global (static) storage
  • ListeningSecurityContextHolderStrategy - Listener-based storage

Default Behaviors:

  • Default strategy is ThreadLocalSecurityContextHolderStrategy
  • Delegating executors wrap existing executors
  • Context is copied (not shared) to child threads
  • Context is cleared after task execution

Threading Model:

  • Thread-safe: each thread has its own context
  • Context propagation is copy-based (not reference-based)
  • Delegating executors are thread-safe

Lifecycle:

  • Context set before task execution
  • Context cleared after task execution
  • Strategy configured once at application startup

Exceptions:

  • None thrown by core framework (wraps existing executors)

Edge Cases:

  • Null security context: Propagates as null (no context)
  • Nested async calls: Each level propagates context independently
  • Thread pool reuse: Context is set per-task (not per-thread)
  • Context modification: Changes in child thread don't affect parent
  • Strategy switching: Must be done before any security operations

Core Delegating Executors

DelegatingSecurityContextExecutor

Wraps an Executor to propagate the current SecurityContext to threads that execute tasks.

package org.springframework.security.concurrent;

class DelegatingSecurityContextExecutor implements Executor {
    DelegatingSecurityContextExecutor(Executor delegate);
    DelegatingSecurityContextExecutor(
        Executor delegate,
        SecurityContext securityContext);

    void execute(Runnable command);
}

Constructors:

DelegatingSecurityContextExecutor(Executor delegate)
  • Creates a delegating executor that propagates the current security context.
DelegatingSecurityContextExecutor(
    Executor delegate,
    SecurityContext securityContext)
  • Creates a delegating executor that propagates a specific security context.

Key Methods:

void execute(Runnable command)
  • Executes the command with the security context set in the execution thread.

Example:

Executor executor = Executors.newFixedThreadPool(10);
DelegatingSecurityContextExecutor secureExecutor =
    new DelegatingSecurityContextExecutor(executor);

// Current security context will be propagated
Authentication auth = new UsernamePasswordAuthenticationToken(
    "user",
    "password",
    AuthorityUtils.createAuthorityList("ROLE_USER")
);
SecurityContextHolder.getContext().setAuthentication(auth);

// Submit task - security context will be available in the task
secureExecutor.execute(() -> {
    // Security context is available here
    Authentication currentAuth = SecurityContextHolder.getContext()
        .getAuthentication();
    // currentAuth == auth
    System.out.println("Executing as: " + currentAuth.getName());
});

With Specific Context:

Executor executor = Executors.newFixedThreadPool(10);

SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication systemAuth = new UsernamePasswordAuthenticationToken(
    "system",
    null,
    AuthorityUtils.createAuthorityList("ROLE_SYSTEM")
);
context.setAuthentication(systemAuth);

DelegatingSecurityContextExecutor systemExecutor =
    new DelegatingSecurityContextExecutor(executor, context);

// Task executes with system context regardless of current context
systemExecutor.execute(() -> {
    Authentication auth = SecurityContextHolder.getContext()
        .getAuthentication();
    // auth.getName() == "system"
});

DelegatingSecurityContextExecutorService

Wraps an ExecutorService to propagate security context, extending DelegatingSecurityContextExecutor.

package org.springframework.security.concurrent;

class DelegatingSecurityContextExecutorService
    extends DelegatingSecurityContextExecutor
    implements ExecutorService {

    DelegatingSecurityContextExecutorService(ExecutorService delegate);
    DelegatingSecurityContextExecutorService(
        ExecutorService delegate,
        SecurityContext securityContext);

    // ExecutorService methods
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(
        Collection<? extends Callable<T>> tasks,
        long timeout,
        TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(
        Collection<? extends Callable<T>> tasks,
        long timeout,
        TimeUnit unit)
        throws InterruptedException, ExecutionException;
}

Constructors:

DelegatingSecurityContextExecutorService(ExecutorService delegate)
DelegatingSecurityContextExecutorService(
    ExecutorService delegate,
    SecurityContext securityContext)

Example:

ExecutorService executorService = Executors.newCachedThreadPool();
DelegatingSecurityContextExecutorService secureExecutorService =
    new DelegatingSecurityContextExecutorService(executorService);

// Set current context
Authentication auth = ...;
SecurityContextHolder.getContext().setAuthentication(auth);

// Submit callable with context propagation
Future<String> future = secureExecutorService.submit(() -> {
    Authentication currentAuth = SecurityContextHolder.getContext()
        .getAuthentication();
    return "Executed by: " + currentAuth.getName();
});

String result = future.get();
// result contains the authenticated user's name

// Submit multiple tasks
List<Callable<String>> tasks = Arrays.asList(
    () -> {
        Authentication a = SecurityContextHolder.getContext()
            .getAuthentication();
        return "Task 1: " + a.getName();
    },
    () -> {
        Authentication a = SecurityContextHolder.getContext()
            .getAuthentication();
        return "Task 2: " + a.getName();
    }
);

List<Future<String>> futures = secureExecutorService.invokeAll(tasks);
for (Future<String> f : futures) {
    System.out.println(f.get());
}

// Cleanup
secureExecutorService.shutdown();

DelegatingSecurityContextTaskExecutor

Wraps a Spring TaskExecutor to propagate security context.

package org.springframework.security.concurrent;

class DelegatingSecurityContextTaskExecutor implements TaskExecutor {
    DelegatingSecurityContextTaskExecutor(TaskExecutor delegate);
    DelegatingSecurityContextTaskExecutor(
        TaskExecutor delegate,
        SecurityContext securityContext);

    void execute(Runnable task);
}

Constructors:

DelegatingSecurityContextTaskExecutor(TaskExecutor delegate)
DelegatingSecurityContextTaskExecutor(
    TaskExecutor delegate,
    SecurityContext securityContext)

Example:

@Configuration
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    @Bean
    public TaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.initialize();

        // Wrap with security context propagation
        return new DelegatingSecurityContextTaskExecutor(executor);
    }
}

// Usage with @Async
@Service
public class AsyncService {

    @Async
    public void asyncMethod() {
        // Security context is available here
        Authentication auth = SecurityContextHolder.getContext()
            .getAuthentication();
        // Process with authenticated context
    }
}

DelegatingSecurityContextAsyncTaskExecutor

Wraps a Spring AsyncTaskExecutor to propagate security context.

package org.springframework.security.concurrent;

class DelegatingSecurityContextAsyncTaskExecutor
    extends DelegatingSecurityContextTaskExecutor
    implements AsyncTaskExecutor {

    DelegatingSecurityContextAsyncTaskExecutor(AsyncTaskExecutor delegate);
    DelegatingSecurityContextAsyncTaskExecutor(
        AsyncTaskExecutor delegate,
        SecurityContext securityContext);

    // AsyncTaskExecutor methods
    void execute(Runnable task, long startTimeout);
    Future<?> submit(Runnable task);
    <T> Future<T> submit(Callable<T> task);
}

Example:

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.initialize();

DelegatingSecurityContextAsyncTaskExecutor secureExecutor =
    new DelegatingSecurityContextAsyncTaskExecutor(executor);

// Submit with timeout
secureExecutor.execute(() -> {
    Authentication auth = SecurityContextHolder.getContext()
        .getAuthentication();
    // Process
}, 5000);

// Submit callable
Future<String> future = secureExecutor.submit(() -> {
    Authentication auth = SecurityContextHolder.getContext()
        .getAuthentication();
    return "Result from: " + auth.getName();
});

DelegatingSecurityContextTaskScheduler

Wraps a Spring TaskScheduler to propagate security context to scheduled tasks.

package org.springframework.security.concurrent;

class DelegatingSecurityContextTaskScheduler implements TaskScheduler {
    DelegatingSecurityContextTaskScheduler(TaskScheduler delegate);
    DelegatingSecurityContextTaskScheduler(
        TaskScheduler delegate,
        SecurityContext securityContext);

    // TaskScheduler methods
    ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
    ScheduledFuture<?> schedule(Runnable task, Date startTime);
    <T> ScheduledFuture<T> schedule(Callable<T> task, Date startTime);
    ScheduledFuture<?> scheduleAtFixedRate(
        Runnable task,
        Date startTime,
        long period);
    ScheduledFuture<?> scheduleAtFixedRate(
        Runnable task,
        long period);
    ScheduledFuture<?> scheduleWithFixedDelay(
        Runnable task,
        Date startTime,
        long delay);
    ScheduledFuture<?> scheduleWithFixedDelay(
        Runnable task,
        long delay);
}

Example:

ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.initialize();

DelegatingSecurityContextTaskScheduler secureScheduler =
    new DelegatingSecurityContextTaskScheduler(scheduler);

// Schedule with fixed rate
secureScheduler.scheduleAtFixedRate(() -> {
    Authentication auth = SecurityContextHolder.getContext()
        .getAuthentication();
    // Scheduled task with security context
    System.out.println("Scheduled task by: " + auth.getName());
}, 1000);

// Schedule with delay
secureScheduler.scheduleWithFixedDelay(() -> {
    Authentication auth = SecurityContextHolder.getContext()
        .getAuthentication();
    // Process
}, 5000);

// Schedule at specific time
Date startTime = new Date(System.currentTimeMillis() + 60000);
secureScheduler.schedule(() -> {
    Authentication auth = SecurityContextHolder.getContext()
        .getAuthentication();
    // One-time scheduled task
}, startTime);

Security Context Holder Strategies

SecurityContextHolderStrategy

Strategy interface for storing and retrieving SecurityContext.

package org.springframework.security.core.context;

interface SecurityContextHolderStrategy {
    void clearContext();
    SecurityContext getContext();
    void setContext(SecurityContext context);
    SecurityContext createEmptyContext();
}

Key Methods:

void clearContext()
  • Clears the current security context.
SecurityContext getContext()
  • Returns the current security context (never null).
void setContext(SecurityContext context)
  • Sets the security context.
SecurityContext createEmptyContext()
  • Creates a new empty security context.

ThreadLocalSecurityContextHolderStrategy

Default strategy using ThreadLocal storage. Each thread has its own security context.

package org.springframework.security.core.context;

class ThreadLocalSecurityContextHolderStrategy
    implements SecurityContextHolderStrategy {
    ThreadLocalSecurityContextHolderStrategy();

    void clearContext();
    SecurityContext getContext();
    void setContext(SecurityContext context);
    SecurityContext createEmptyContext();
}

Behavior:

  • Each thread has independent security context
  • Context is not inherited by child threads
  • Default strategy for most applications

Example:

// This is the default - no explicit configuration needed
// But can be set explicitly:
SecurityContextHolder.setStrategyName(
    SecurityContextHolder.MODE_THREADLOCAL
);
// Or:
SecurityContextHolder.setContextHolderStrategy(
    new ThreadLocalSecurityContextHolderStrategy()
);

InheritableThreadLocalSecurityContextHolderStrategy

Strategy using InheritableThreadLocal storage. Security context is inherited by child threads.

package org.springframework.security.core.context;

class InheritableThreadLocalSecurityContextHolderStrategy
    implements SecurityContextHolderStrategy {
    InheritableThreadLocalSecurityContextHolderStrategy();

    void clearContext();
    SecurityContext getContext();
    void setContext(SecurityContext context);
    SecurityContext createEmptyContext();
}

Behavior:

  • Security context is inherited by child threads
  • Changes in child threads don't affect parent
  • Useful when creating threads directly (not via executors)

Example:

// Set strategy
SecurityContextHolder.setStrategyName(
    SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
);

// Set context in parent thread
Authentication auth = ...;
SecurityContextHolder.getContext().setAuthentication(auth);

// Create child thread - context is inherited
Thread childThread = new Thread(() -> {
    // Security context is available here
    Authentication inheritedAuth = SecurityContextHolder.getContext()
        .getAuthentication();
    // inheritedAuth == auth
});

childThread.start();

Note: When using executors, prefer DelegatingSecurityContextExecutor over inheritable thread local, as executors reuse threads and context inheritance can cause issues.

GlobalSecurityContextHolderStrategy

Strategy using a single global (static) security context shared across all threads.

package org.springframework.security.core.context;

class GlobalSecurityContextHolderStrategy
    implements SecurityContextHolderStrategy {
    GlobalSecurityContextHolderStrategy();

    void clearContext();
    SecurityContext getContext();
    void setContext(SecurityContext context);
    SecurityContext createEmptyContext();
}

Behavior:

  • Single security context shared by all threads
  • Not thread-safe - use only in single-threaded environments
  • Rarely used in practice

Example:

// Set strategy (NOT recommended for multi-threaded apps)
SecurityContextHolder.setStrategyName(
    SecurityContextHolder.MODE_GLOBAL
);

// All threads share the same context
SecurityContextHolder.getContext().setAuthentication(auth);

ListeningSecurityContextHolderStrategy

Strategy that notifies listeners when security context changes.

package org.springframework.security.core.context;

class ListeningSecurityContextHolderStrategy
    implements SecurityContextHolderStrategy {
    ListeningSecurityContextHolderStrategy(
        SecurityContextHolderStrategy delegate);

    void addListener(SecurityContextChangedListener listener);
    void removeListener(SecurityContextChangedListener listener);

    void clearContext();
    SecurityContext getContext();
    void setContext(SecurityContext context);
    SecurityContext createEmptyContext();
}

Constructor:

ListeningSecurityContextHolderStrategy(
    SecurityContextHolderStrategy delegate)
  • Wraps a delegate strategy and adds listener support.

Key Methods:

void addListener(SecurityContextChangedListener listener)
void removeListener(SecurityContextChangedListener listener)

Example:

ThreadLocalSecurityContextHolderStrategy delegate =
    new ThreadLocalSecurityContextHolderStrategy();

ListeningSecurityContextHolderStrategy strategy =
    new ListeningSecurityContextHolderStrategy(delegate);

// Add listener
strategy.addListener((oldContext, newContext) -> {
    System.out.println("Security context changed");
    if (oldContext != null) {
        System.out.println("Old: " + oldContext.getAuthentication());
    }
    if (newContext != null) {
        System.out.println("New: " + newContext.getAuthentication());
    }
});

SecurityContextHolder.setContextHolderStrategy(strategy);

// Context changes will trigger listener
Authentication auth = ...;
SecurityContextHolder.getContext().setAuthentication(auth);
// Listener is notified

SecurityContextChangedListener

Listener interface for security context changes.

package org.springframework.security.core.context;

interface SecurityContextChangedListener {
    void securityContextChanged(
        @Nullable SecurityContext oldContext,
        @Nullable SecurityContext newContext);
}

Complete Configuration Example

@Configuration
@EnableAsync
public class AsyncSecurityConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();

        // Wrap with security context propagation
        return new DelegatingSecurityContextTaskExecutor(executor);
    }

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(5);
        scheduler.setThreadNamePrefix("scheduled-");
        scheduler.initialize();

        return new DelegatingSecurityContextTaskScheduler(scheduler);
    }

    @Bean
    public ExecutorService executorService() {
        ExecutorService service = Executors.newFixedThreadPool(10);
        return new DelegatingSecurityContextExecutorService(service);
    }
}

// Usage
@Service
public class BackgroundService {

    @Autowired
    private TaskExecutor taskExecutor;

    @Autowired
    private TaskScheduler taskScheduler;

    public void executeBackgroundTask() {
        // Current security context will be propagated
        taskExecutor.execute(() -> {
            Authentication auth = SecurityContextHolder.getContext()
                .getAuthentication();
            // Process with authenticated context
        });
    }

    public void scheduleTask() {
        taskScheduler.scheduleAtFixedRate(() -> {
            Authentication auth = SecurityContextHolder.getContext()
                .getAuthentication();
            // Scheduled task with security context
        }, 1000);
    }
}

Best Practices

  1. Use Delegating Executors: Always wrap executors with delegating variants when security context is needed in background threads.

  2. Avoid InheritableThreadLocal with Executors: Executors reuse threads, so inheritable thread local can cause context leakage. Use delegating executors instead.

  3. Clear Context After Use: Delegating executors automatically clear context after task execution, but be aware when manually managing context.

  4. Context is Copied: Security context is copied (not shared) to child threads, so modifications in child threads don't affect parent.

// Good: Use delegating executor
ExecutorService service = Executors.newFixedThreadPool(10);
DelegatingSecurityContextExecutorService secureService =
    new DelegatingSecurityContextExecutorService(service);

// Bad: Relying on inheritable thread local with executors
SecurityContextHolder.setStrategyName(
    SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
);
// Executor threads are reused, context can leak between tasks

Package

  • org.springframework.security.concurrent
  • org.springframework.security.core.context