spring-projects/spring-framework · error · IllegalStateException

BeanFactory must be set on {} to access qualified executor '

Error message

BeanFactory must be set on {} to access qualified executor '{}'

What it means

Thrown by findQualifiedExecutor (line 213-216) when an async method specifies an executor qualifier (e.g. @Async("customExecutor")) but the beanFactory field on the interceptor/advice is null. Looking up a qualified executor requires a BeanFactory to search; without one the lookup cannot proceed and an IllegalStateException is raised.

Source

Thrown at spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java:214

	 * been specified and that the {@linkplain #setExecutor(Executor) default executor}
	 * should be used.
	 * @param method the method to inspect for executor qualifier metadata
	 * @return the qualifier if specified, otherwise empty String or {@code null}
	 * @see #determineAsyncExecutor(Method)
	 * @see #findQualifiedExecutor(BeanFactory, String)
	 */
	protected abstract @Nullable String getExecutorQualifier(Method method);

	/**
	 * Retrieve a target executor for the given qualifier.
	 * @param qualifier the qualifier to resolve
	 * @return the target executor, or {@code null} if none available
	 * @since 4.2.6
	 * @see #getExecutorQualifier(Method)
	 */
	protected @Nullable Executor findQualifiedExecutor(@Nullable BeanFactory beanFactory, String qualifier) {
		if (beanFactory == null) {
			throw new IllegalStateException("BeanFactory must be set on " + getClass().getSimpleName() +
					" to access qualified executor '" + qualifier + "'");
		}
		return BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, Executor.class, qualifier);
	}

	/**
	 * Retrieve or build a default executor for this advice instance.
	 * <p>An executor returned from here will be cached for further use.
	 * <p>The default implementation searches for a unique {@link TaskExecutor} bean
	 * in the context, or for an {@link Executor} bean named "taskExecutor" otherwise.
	 * If neither of the two is resolvable, this implementation will return {@code null}.
	 * @param beanFactory the BeanFactory to use for a default executor lookup
	 * @return the default executor, or {@code null} if none available
	 * @since 4.2.6
	 * @see #findQualifiedExecutor(BeanFactory, String)
	 * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME
	 */
	protected @Nullable Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {

View on GitHub (pinned to 69bf83ad71)

Solutions

  1. Register the interceptor/advice as a Spring bean so that setBeanFactory is invoked automatically (BeanFactoryAware).
  2. If constructing manually, call 'interceptor.setBeanFactory(beanFactory)' before use.
  3. Remove the executor qualifier from @Async and instead set an explicit default executor via setExecutor(...) so no BeanFactory lookup is needed.
  4. Ensure the qualifier value matches a real Executor bean name in the context.

Example fix

// before
AsyncExecutionInterceptor interceptor = new AsyncExecutionInterceptor(null);
// @Async("custom") method invoked -> BeanFactory is null

// after
AsyncExecutionInterceptor interceptor = new AsyncExecutionInterceptor(null);
interceptor.setBeanFactory(context.getBeanFactory());
// or register it as a bean so BeanFactoryAware wiring happens
Defensive patterns

Strategy: validation

Validate before calling

AsyncExecutionInterceptor interceptor = new AsyncExecutionInterceptor(null);
if (methodHasQualifier && interceptor.getBeanFactory() == null) {
    interceptor.setBeanFactory(context.getBeanFactory());
}

Prevention

When it happens

Trigger: Using @Async("qualifier") on a method, or a custom subclass of AsyncExecutionAspectSupport whose getExecutorQualifier returns a non-empty string, while the advice/interceptor was constructed directly (new AsyncExecutionInterceptor(...)) and never had setBeanFactory(...) called (i.e. it was not registered as a BeanFactoryAware bean inside a Spring container).

Common situations: Manually constructing an async interceptor outside the container in tests or a non-Spring-managed setup. Registering the interceptor as a plain bean but losing the BeanFactoryAware callback. Using the @Async annotation's qualifier attribute in a context where the AsyncAnnotationBeanPostProcessor did not wire the BeanFactory onto the advice.

Related errors


AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09). Data as JSON: /api/errors/26335d94530bba63. Report an issue: GitHub.