Using @Qualifier to Resolve Bean Conflicts in Spring

Author:

When working on a Spring project, you might encounter a situation where you have multiple bean implementations for the same type.

This can cause an error because Spring doesn’t know which bean to inject. To solve this, we use the @Qualifier annotation.

The Problem

Let’s say you have an interface MessageService that your application uses to send messages, and you have multiple ways to send messages, such as Email, SMS, Push, etc.

For each of these channels, you have an implementation of the interface like EmailMessageService and SmsMessageService.

Consider this scenario. You have a MessageService that defines a standard to send messages for all methods.


public interface MessageService {
    void sendMessage(String to, String message);
}

@Service("emailService")
public class EmailMessageService implements MessageService {
    @Override
    public void sendMessage(String to, String message) {
        System.out.println("Sending email to: " + to + " with message: " + message);
    }
}

@Service("smsService")
public class SmsMessageService implements MessageService {
    @Override
    public void sendMessage(String to, String message) {
        System.out.println("Sending SMS to: " + to + " with message: " + message);
    }
}


If you try to use these services in the components like this:


@Service
public class OrderService {

    private final MessageService messageService;

    @Autowired
    public OrderService(MessageService messageService) {
        this.messageService = messageService;
    }

    public void sendOrderConfirmation(String email, String message) {
        messageService.sendMessage(email, message);
    }
}

@Service
public class ForgetPasswordService {

    private final MessageService messageService;

    @Autowired
    public ForgetPasswordService(MessageService messageService) {
        this.messageService = messageService;
    }

    public void sendResetPasswordLink(String phoneNumber, String message) {
        messageService.sendMessage(phoneNumber, message);
    }

}


You will get an error, and a message like that will pop up at your application startup:

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.spring.mastery.qualifier.OrderService required a single bean, but 2 were found:
    - emailService: defined in file [...\\EmailMessageService.class]
    - smsService: defined in file [...\\SmsMessageService.class]


Spring doesn’t know which bean to use. There are two beans of the same type.

Using @Qualifier

By using @Qualifier, you can specify the name of the bean to be injected. Here’s how you can do it:


@Service
public class OrderService {

    private final MessageService messageService;

    @Autowired
    public OrderService(@Qualifier("emailService") MessageService messageService) {
        this.messageService = messageService;
    }

    public void sendOrderConfirmation(String email, String message) {
        messageService.sendMessage(email, message);
    }
}

@Service
public class ForgetPasswordService {

    private final MessageService messageService;

    @Autowired
    public ForgetPasswordService(@Qualifier("smsService") MessageService messageService) {
        this.messageService = messageService;
    }

    public void sendResetPasswordLink(String phoneNumber, String message) {
        messageService.sendMessage(phoneNumber, message);
    }

}


Now, Spring knows which bean to inject in each component, allowing you to have multiple implementations of the same bean.

Variations

There are a few other ways to help Spring identify which bean to inject.

Field Injection with @Autowired and @Qualifier:

This is one of the most common ones.


@Autowired
@Qualifier("emailService")
private MessageService messageService;


Naming the Field:

If you don’t want to use the @Qualifier annotation explicitly, you can name the field with the same name as the bean definition:


@Service("smsService")
public class SmsMessageService implements MessageService {
    // some code here
}

@Service
public class ForgetPasswordService {

    private final MessageService smsService;

    @Autowired
    public ForgetPasswordService(MessageService smsService) {
        this.smsService = smsService;
    }
}


It’s important to remember that using this method, your field name is coupled with the qualifier name you defined, if you change the qualifier name or the field name, it might break everything. So use it with caution.

Defining Beans in Configuration Classes:

If you have a configuration class like this, you can define the bean qualifier name in the @Bean annotation.


@Configuration
public class QualifierConfig {

    @Bean("beanDefinition")
    public BeanDefinition beanDefinition() {
        return new BeanDefinition();
    }
}


Conclusion

Have you already faced a problem like this in your project? Did you use the @Qualifier? Have you seen this annotation in your daily job? Let me know in the comments, or on social media!

If you like this topic, make sure to follow me. In the following days, I’ll be explaining more about Spring annotations! Stay tuned!

Willian Moya (@WillianFMoya) / X (twitter.com)

Willian Ferreira Moya | LinkedIn

Leave a Reply

Your email address will not be published. Required fields are marked *