javaspringjavabeans

Spring expected single matching bean but found 2


I have a Spring configuration class like this where I need to configure 2 different Queue:

@Configuration
public class DataQueue {

    /**
     * Queue to store the {@link SlowVars} read from the PLC
     * @return the queue
     */
    @Bean
    Set<JsonNode> slowVarsQueue() {
        return new CopyOnWriteArraySet<>();
    }

    /**
     * Queue to store the {@link JsonNode} events
     * @return the queue
     */
    @Bean
    Set<JsonNode> eventsQueue() {
        return new CopyOnWriteArraySet<>();
    }
}

In the class where I need to use one queue I wrote:

@Log4j2
@RestController
@AllArgsConstructor
public class EventsApiImpl implements EventsApi {

    private Set<JsonNode> eventsQueue;
    private MariaEventService mariaEventService;
    private EventManipulationService eventManipulationService;
    private ObjectMapper mapper;

// lots of methods

}

unfortunately Springs raise an error starting:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean
with name 'EventsApiImpl': Unsatisfied dependency expressed through constructor parameter 0:
No qualifying bean of type 'java.util.Set[com.fasterxml.jackson.databind.JsonNode]' available: 
expected single matching bean but found 2: slowVarsQueue,eventsQueue

The two beans has different names one is slowVarsQueue and the other is eventsQueue, why Spring complain ?


Solution

  • As your error states, spring can't decide on which bean to use. Both of your beans provide the same datatype, so you have to define which one should be used by spring.

    The easiest way is to just add @Primary to the bean you want to use. You could also use @Qualifier, to select specifically which bean you want to use.

    Edit: Code-solutions for your specific case:

    1) Use @Primary for one of the beans

    @Configuration
    public class DataQueue {
    
        @Bean
        @Primary
        Set<JsonNode> slowVarsQueue() {
            return new CopyOnWriteArraySet<>();
        }
    
        @Bean
        Set<JsonNode> eventsQueue() {
            return new CopyOnWriteArraySet<>();
        }
    }
    

    This solution limits your use of the eventsQueue() bean, meaning it will only be accessible by name. Any code that requires a bean of type Set<JsonNode>, and does not specify the name of the bean, will use slowVarsQueue(). If you want to use eventsQueue() at a specific location, you would need to reference it specifically by name with @Qualifier or other means of referencing beans by name.

    2) Use @Qualifier in the constructor arguments

    @Log4j2
    @RestController
    public class EventsApiImpl implements EventsApi {
    
        private Set<JsonNode> eventsQueue;
        private MariaEventService mariaEventService;
        private EventManipulationService eventManipulationService;
        private ObjectMapper mapper;
    
        public EventsApiImpl(@Qualifier("eventsQueue") Set<JsonNode> eventsQueue, MariaEventService mariaEventService, EventManipulationService eventManipulationService, ObjectMapper mapper)
        {
            this.eventsQueue = eventsQueue;
            this.mariaEventService = mariaEventService;
            this.eventManipulationService = eventManipulationService;
            this.mapper = mapper;
        }
    
    // lots of methods
    
    }
    

    This option still keeps both beans as equally valuable, meaning other parts of your code will still throw the exception if they try to access a bean of type Set<JsonNode>. This has the advantage, that you always know which bean you access. But if you access these beans in many locations in your code, you have to define the @Qualifier in each of these locations.

    3) Use @Primary AND @Qualifier

    Use both of the code-snippets from 1) and 2) for this solution. This has the advantage, that whenever you access a Set<JsonNode> bean anywhere in your code, it will always use slowVarsQueue() without throwing an exception. But if you want to use eventsQueue() in any of those places, you can simply do so, by using @Qualifier("eventsQueue").