ADR-0006: Disambiguate Spring beans by concrete type, not field @Qualifier
- Status: Accepted
- Date: 2026-05-31
- Scope: core (any Spring backend using Lombok constructors)
Context
When several beans are assignable to the same interface (e.g. @EnableAsync +
scheduling expose two TaskExecutor-assignable beans), an injected dependency
needs disambiguation. The reflex is a field-level @Qualifier on a
@RequiredArgsConstructor class — but Lombok generates the constructor, and a
field @Qualifier is only carried onto the generated constructor parameter if a
lombok.config declares it under lombok.copyableAnnotations. These backends
keep no lombok.config (and in Expenses the Docker build copies only
mvnw/pom.xml/.mvn/src anyway), so a field @Qualifier on a Lombok
constructor is silently not honored → Spring sees an ambiguous bean and fails
to start.
Decision
Disambiguate by injecting the concrete type, not a field @Qualifier, on
@RequiredArgsConstructor classes. Inject the bean’s concrete class (which is
unambiguous by type) rather than the shared interface + a qualifier.
Example (Expenses: presentation/chat/webservice/ChatWebService.java): inject the
concrete ThreadPoolTaskExecutor applicationTaskExecutor, not
@Qualifier("applicationTaskExecutor") TaskExecutor — only the former survives
Lombok’s generated constructor here.
If a future need genuinely requires field-level qualifiers on Lombok constructors,
that requires adding a lombok.config with copyableAnnotations and ensuring
it’s copied into the Docker image — neither of which is true today, so don’t rely
on it.
Consequences
- Bean wiring is unambiguous without depending on a
lombok.configwe don’t
maintain; avoids an ambiguous-bean startup failure. - Slightly less flexible than qualifiers, but type-based injection is clearer anyway.
Related
- Build context: the backend
Dockerfile(copiesmvnw/pom.xml/.mvn/srconly) - Operational detail: the backend repo’s
CLAUDE.md