spring-projects/spring-framework · error · IllegalStateException
No executor specified and no default executor set on…
Error message
No executor specified and no default executor set on AsyncExecutionInterceptor either
What it means
Thrown in AsyncExecutionInterceptor.invoke (line 105-107) when determineAsyncExecutor returns null, meaning no executor could be resolved for the method - neither a qualifier-matched executor nor a default. AsyncExecutionInterceptor.getDefaultExecutor (line 155-157) normally falls back to a SimpleAsyncTaskExecutor, so reaching this error means a subclass overrode getDefaultExecutor to return null and no explicit executor was supplied.
Solutions
- Provide an explicit default executor: call setExecutor(new ThreadPoolTaskExecutor()) or return one from an AsyncConfigurer.getAsyncExecutor().
- Define a TaskExecutor bean (or a bean named 'taskExecutor') in the context so getDefaultExecutor can resolve it.
- If you overrode getDefaultExecutor, either keep the SimpleAsyncTaskExecutor fallback or guarantee an executor is always set via setExecutor.
- For @Async("qualifier"), make sure the named Executor bean actually exists.
Example fix
// before
@Configuration @EnableAsync
public class AsyncConfig implements AsyncConfigurer {
public Executor getAsyncExecutor() { return null; } // no fallback bean either
}
// after
@Configuration @EnableAsync
public class AsyncConfig implements AsyncConfigurer {
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
ex.initialize();
return ex;
}
} Defensive patterns
Strategy: validation
Validate before calling
Executor ex = interceptor.getDefaultExecutor(context.getBeanFactory());
if (ex == null) {
ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
t.initialize();
interceptor.setExecutor(t);
} Prevention
- Always provide an Executor via AsyncConfigurer.getAsyncExecutor() or setExecutor(...).
- Keep a 'taskExecutor' bean in the context as a reliable default.
- If overriding getDefaultExecutor, preserve the SimpleAsyncTaskExecutor fallback.
When it happens
Trigger: Subclassing AsyncExecutionInterceptor (or AsyncExecutionAspectSupport) and overriding getDefaultExecutor to return null, while not calling setExecutor(...) with a concrete Executor and not having a resolvable TaskExecutor bean in the context. Invoking the async method then yields a null executor and the IllegalStateException.
Common situations: Disabling the SimpleAsyncTaskExecutor fallback to enforce an explicit executor policy but forgetting to register/qualify one. Removing the 'taskExecutor' bean from a context that previously provided the default. Configuring an AsyncConfigurer that returns null from getAsyncExecutor() while no TaskExecutor bean exists.
Related errors
- BeanFactory must be set on
- Invalid return type for async method (only Future and void…
- Advice must be declared inside an aspect type: Offending…
- Advice object [ ] is neither a supported subinterface of…
- AdvisorAutoProxyCreator requires a…
AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09).
Data as JSON: /api/errors/3768a31f8fa58bb2.
Report an issue: GitHub.
Appendix: source
Thrown at spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java:106
super(defaultExecutor, exceptionHandler);
}
/**
* Intercept the given method invocation, submit the actual calling of the method to
* the correct task executor and return immediately to the caller.
* @param invocation the method to intercept and make asynchronous
* @return {@link Future} if the original method returns {@code Future}; {@code null}
* otherwise.
*/
@Override
public @Nullable Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
final Method userMethod = BridgeMethodResolver.getMostSpecificMethod(invocation.getMethod(), targetClass);
AsyncTaskExecutor executor = determineAsyncExecutor(userMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
}
Callable<Object> task = () -> {
try {
Object result = invocation.proceed();
if (result instanceof Future<?> future) {
return future.get();
}
}
catch (ExecutionException ex) {
Throwable cause = ex.getCause();
handleError(cause == null ? ex : cause, userMethod, invocation.getArguments());
}
catch (Throwable ex) {
handleError(ex, userMethod, invocation.getArguments());
}
return null;View on GitHub (pinned to 69bf83ad71)