Using @Qualifier to Resolve Bean Conflicts in Spring

tiuwill

Willian Ferreira Moya

Posted on June 19, 2024

Using @Qualifier to Resolve Bean Conflicts in Spring

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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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);
    }

}

Enter fullscreen mode Exit fullscreen mode

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

bashCopy code
***************************
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]

Enter fullscreen mode Exit fullscreen mode

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);
    }

}

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;
    }
}

Enter fullscreen mode Exit fullscreen mode

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();
    }
}

Enter fullscreen mode Exit fullscreen mode

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!

Follow me!

💖 💪 🙅 🚩
tiuwill
Willian Ferreira Moya

Posted on June 19, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related