Use when adding or debugging Spring @Async work, executor saturation, asynchronous failures or lost thread context. Not a substitute for durable jobs or reactive pipelines.
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Is that skill safe to install?
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
Are you the author of Async Execution?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/rrezartprebreza-async-execution)
---
name: async-execution
description: >
Use when adding or debugging Spring @Async work, executor saturation, asynchronous failures or lost thread context. Not a substitute for durable jobs or reactive pipelines.
---
# Async Execution
## Spring Boot 3 baseline
Use Framework 6 APIs and the project's Boot BOM. The shipped examples compile on Java 17.
Boot 3.2+ can opt into virtual threads on Java 21; do not assume this on earlier Boot 3 releases.
Check the exact minor before copying newer task-execution properties.
## Choose the execution boundary
First decide whether losing work on process exit is acceptable. In-memory async work is not
a durable queue. Required notifications, payments and accepted background jobs need persisted
intent plus recovery, not just an AFTER_COMMIT listener or an HTTP 202 response.
Keep an existing broker or job framework when it already owns this contract.
Enable async interception once and call a separate Spring bean through its proxy. A same-class
call does not become asynchronous. Use a named executor and return CompletableFuture when the
caller must observe failure. A void method needs an AsyncUncaughtExceptionHandler; the caller
cannot catch its later failure. Rejection can happen synchronously during submission.
See [the bounded executor](templates/ReportExecutorConfiguration.java) and
[the worker](templates/ReportTasks.java). Sizes are example budgets, not universal defaults.
[Good usage](examples/good-report-call.java) separates rejected submissions from failed work;
[bad usage](examples/bad-self-invocation.java) hides both the execution and capacity problems.
## Capacity and lifecycle
Bound threads and queued work against downstream capacity and latency targets. A large queue
can hide overload and delay growth beyond the core pool. Define a visible rejection outcome;
do not silently discard required work. CallerRunsPolicy can move blocking work onto an HTTP
or event-loop thread, so it is not a universal backpressure fix.
A custom Executor can change Boot auto-configuration. Preserve the application's existing
applicationTaskExecutor and MVC/WebFlux integration; do not paste the sample configuration
over it. The sample explicitly selects reportExecutor and only covers that worker.
Virtual threads do not increase database connections or remote quotas: bound outstanding work
even without a platform-thread pool. Configure I/O timeouts and bounded shutdown separately.
Future cancellation/timeout is not proof that an external operation stopped.
## State and transactions
Pass immutable IDs or snapshots, not request objects, managed entities or lazy collections.
The caller's thread-bound transaction does not move with the task. Put any worker transaction
on a separate proxied service and load its state there. Starting async work before commit can
observe missing data; after-commit dispatch still has a crash-loss window.
Tenant, MDC and security state need deliberate propagation and cleanup. Capture only required,
trusted context; restore prior worker state in finally. Use Spring Security's delegating
executor support when the operation must run with the submitting security context. Do not
use inheritable thread locals as a pool-wide tenant or authentication strategy.
## Verification
Test via the actual proxy: worker thread selection, failed futures, saturation rejection and
shutdown. Use latches with deadlines, not sleep-based assumptions. Test context cleanup with
successive users on the same worker and database visibility at the commit boundary. Report
which tests were actually run; the provided fixture does not certify durable delivery.
## Gotchas
- Agent calls its own @Async method - invoke a separate managed bean through the proxy.
- Agent calls supplyAsync without an executor inside the worker - avoid a second common-pool hop.
- Agent treats an unbounded queue as overload protection - set and test an explicit capacity budget.
- Agent catches only future failures - handle synchronous task rejection too.
- Agent assumes AFTER_COMMIT plus async guarantees delivery - persist required work for recovery.
- Agent copies tenant or security state without cleanup - prevent leakage between pooled tasks.
## Official sources
- [Boot 3 task execution](https://docs.spring.io/spring-boot/3.5/reference/features/task-execution-and-scheduling.html)
- [Framework 6 scheduling and async interception](https://docs.spring.io/spring-framework/reference/6.2/integration/scheduling.html)
- [Async return types and executor selection](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html)
- [ThreadPoolTaskExecutor capacity](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html)
- [Spring Security concurrency support](https://docs.spring.io/spring-security/reference/features/integrations/concurrency.html)
- [Transaction-bound events](https://docs.spring.io/spring-framework/reference/data-access/transaction/event.html)