Spring boot autowiring an interface with multiple implementations

As mentioned in the comments, by using the @Qualifier annotation, you can distinguish different implementations as described in the docs.

For testing, you can use also do the same. For example:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class MyClassTests {

        @Autowired
        private MyClass testClass;
        @MockBean
        @Qualifier("default")
        private MyImplementation defaultImpl;

        @Test
        public void givenMultipleImpl_whenAutowiring_thenReturnDefaultImpl() {
    // your test here....
    }
}

Assume that you have a GreetingService

public interface GreetingService {
    void doGreetings();
}

And you have 2 implementations HelloService

@Service
@Slf4j
public class HelloService implements GreetingService{
    @Override
    public void doGreetings() {
        log.info("Hello world!");       
    }
}

and HiService

@Slf4j
@Service
public class HiService implements GreetingService{
    @Override
    public void doGreetings() {
        log.info("Hi world!");
    }
}

Then you have another interface, which is BusinessService to call some business

public interface BusinessService {
    void doGreetings();
}

There are some ways to do that

#1. Use @Autowired

@Component
public class BusinessServiceImpl implements BusinessService{
    
    @Autowired
    private GreetingService hiService; // Spring automatically maps the name for you, if you don't want to change it.
    
    @Autowired
    private GreetingService helloService;
    

    @Override
    public void doGreetings() {
        hiService.doGreetings();
        helloService.doGreetings();
    }

}

In case you need to change your implementation bean name, refer to other answers, by setting the name to your bean, for example @Service("myCustomName") and applying @Qualifier("myCustomName")

#2. You can also use constructor injection

@Component
public class BusinessServiceImpl implements BusinessService {

    private final GreetingService hiService;

    private final GreetingService helloService;

    public BusinessServiceImpl(GreetingService hiService, GreetingService helloService) {
        this.hiService = hiService;
        this.helloService = helloService;
    }

    @Override
    public void doGreetings() {
        hiService.doGreetings();
        helloService.doGreetings();
    }

}

This can be

public BusinessServiceImpl(@Qualifier("hiService") GreetingService hiService, @Qualifier("helloService") GreetingService helloService)

But I am using Spring Boot 2.6.5 and

public BusinessServiceImpl(GreetingService hiService, GreetingService helloService)

is working fine, since Spring automatically get the names for us.

#3. You can also use Map for this

@Component
@RequiredArgsConstructor
public class BusinessServiceImpl implements BusinessService {

    private final Map<String, GreetingService> servicesMap; // Spring automatically get the bean name as key

    @Override
    public void doGreetings() {
        servicesMap.get("hiService").doGreetings();
        servicesMap.get("helloService").doGreetings();
    }

}

List also works fine if you run all the services. But there is a case that you want to get some specific implementation, you need to define a name for it or something like that. My reference is here

For this one, I use @RequiredArgsConstructor from Lombok.


Use @Qualifier annotation is used to differentiate beans of the same interface
Take look at Spring Boot documentation
Also, to inject all beans of the same interface, just autowire List of interface
(The same way in Spring / Spring Boot / SpringBootTest)
Example below:

@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
}

public interface MyService {

    void doWork();

}

@Service
@Qualifier("firstService")
public static class FirstServiceImpl implements MyService {

    @Override
    public void doWork() {
        System.out.println("firstService work");
    }

}

@Service
@Qualifier("secondService")
public static class SecondServiceImpl implements MyService {

    @Override
    public void doWork() {
        System.out.println("secondService work");
    }

}

@Component
public static class FirstManager {

    private final MyService myService;

    @Autowired // inject FirstServiceImpl
    public FirstManager(@Qualifier("firstService") MyService myService) {
        this.myService = myService;
    }

    @PostConstruct
    public void startWork() {
        System.out.println("firstManager start work");
        myService.doWork();
    }

}

@Component
public static class SecondManager {

    private final List<MyService> myServices;

    @Autowired // inject MyService all implementations
    public SecondManager(List<MyService> myServices) {
        this.myServices = myServices;
    }

    @PostConstruct
    public void startWork() {
        System.out.println("secondManager start work");
        myServices.forEach(MyService::doWork);
    }

}

}

For the second part of your question, take look at this useful answers first / second


You can also make it work by giving it the name of the implementation.

Eg:

@Autowired
MyService firstService;

@Autowired
MyService secondService;