spring-projects/spring-framework · error · IllegalStateException

No executor specified and no default executor set on AsyncEx

Error message

No executor specified and no default executor set on AsyncExecutionInterceptor either

What it means

Thrown by AsyncExecutionInterceptor.invoke when determineAsyncExecutor returns null: no qualifier was set, no explicit executor was supplied, and getDefaultExecutor resolved to null. AsyncExecutionInterceptor normally falls back to a SimpleAsyncTaskExecutor, so reaching this branch means a custom subclass overrode getDefaultExecutor to return null, or the executor was explicitly cleared.

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 e8729d0438)

Solutions

  1. Define a TaskExecutor bean (optionally named 'taskExecutor') in the ApplicationContext so getDefaultExecutor resolves it.
  2. Pass a concrete Executor to the constructor or call setExecutor(executor) on the interceptor.
  3. If subclassing, keep AsyncExecutionInterceptor's SimpleAsyncTaskExecutor fallback rather than returning null from getDefaultExecutor.

Example fix

// before
@Override
protected Executor getDefaultExecutor(BeanFactory bf) {
    return null; // fail-fast -> error 102
}

// after
@Override
protected Executor getDefaultExecutor(BeanFactory bf) {
    Executor e = super.getDefaultExecutor(bf);
    return (e != null ? e : new SimpleAsyncTaskExecutor());
}
Defensive patterns

Strategy: validation

Validate before calling

Executor ex = interceptor.determineAsyncExecutor(method); // or resolve eagerly
if (ex == null) {
    ex = new SimpleAsyncTaskExecutor();
    interceptor.setExecutor(ex);
}

Prevention

When it happens

Trigger: A subclass of AsyncExecutionAspectSupport overrides getDefaultExecutor to return null (instead of falling back) and no executor was passed via constructor/setExecutor; or setExecutor(null) was called and the supplier yields null.

Common situations: Custom async aspect that tries to fail-fast when no TaskExecutor bean is present; programmatic AOP wiring that forgets to provide an Executor; disabling the SimpleAsyncTaskExecutor fallback.

Related errors


AI-assisted analysis of spring-projects/spring-framework@e8729d0438 (2026-08-04). Data as JSON: /data/errors/3768a31f8fa58bb2.json. Report an issue: GitHub.