3.5 Bean Qualifiers

If you have multiple possible implementations for a given interface that you want to inject, you need to use a qualifier.

Once again Micronaut leverages JSR-330 and the Qualifier and Named annotations to support this use case.

Qualifying By Name

To qualify by name you can use the Named annotation. For example, consider the following classes:

  1. public interface Engine { (1)
  2. int getCylinders();
  3. String start();
  4. }
  5. @Singleton
  6. public class V6Engine implements Engine { (2)
  7. public String start() {
  8. return "Starting V6";
  9. }
  10. public int getCylinders() {
  11. return 6;
  12. }
  13. }
  14. @Singleton
  15. public class V8Engine implements Engine {
  16. public String start() {
  17. return "Starting V8";
  18. }
  19. public int getCylinders() {
  20. return 8;
  21. }
  22. }
  23. @Singleton
  24. public class Vehicle {
  25. private final Engine engine;
  26. @Inject
  27. public Vehicle(@Named("v8") Engine engine) {(4)
  28. this.engine = engine;
  29. }
  30. public String start() {
  31. return engine.start();(5)
  32. }
  33. }
  1. interface Engine { (1)
  2. int getCylinders()
  3. String start()
  4. }
  5. @Singleton
  6. class V6Engine implements Engine { (2)
  7. int cylinders = 6
  8. String start() {
  9. "Starting V6"
  10. }
  11. }
  12. @Singleton
  13. class V8Engine implements Engine { (3)
  14. int cylinders = 8
  15. String start() {
  16. "Starting V8"
  17. }
  18. }
  19. @Singleton
  20. class Vehicle {
  21. final Engine engine
  22. @Inject Vehicle(@Named('v8') Engine engine) { (4)
  23. this.engine = engine
  24. }
  25. String start() {
  26. engine.start() (5)
  27. }
  28. }
  1. interface Engine { (1)
  2. val cylinders: Int
  3. fun start(): String
  4. }
  5. @Singleton
  6. class V6Engine : Engine { (2)
  7. override var cylinders: Int = 6
  8. override fun start(): String {
  9. return "Starting V6"
  10. }
  11. }
  12. @Singleton
  13. class V8Engine : Engine {
  14. override var cylinders: Int = 8
  15. override fun start(): String {
  16. return "Starting V8"
  17. }
  18. }
  19. @Singleton
  20. class Vehicle @Inject
  21. constructor(@param:Named("v8") private val engine: Engine)(4)
  22. {
  23. fun start(): String {
  24. return engine.start()(5)
  25. }
  26. }
1The Engine interface defines the common contract
2The V6Engine class is the first implementation
3The V8Engine class is the second implementation
4The Named annotation is used to indicate the V8Engine implementation is required
5Calling the start method prints: “Starting V8”

You can also declare @Named at the class level of a bean to explicitly define the name of the bean.

Qualifying By Annotation

In addition to being able to qualify by name, you can build your own qualifiers using the Qualifier annotation. For example, consider the following annotation:

  1. import javax.inject.Qualifier;
  2. import java.lang.annotation.Retention;
  3. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  4. @Qualifier
  5. @Retention(RUNTIME)
  6. public @interface V8 {
  7. }
  1. import javax.inject.Qualifier
  2. import java.lang.annotation.Retention
  3. import static java.lang.annotation.RetentionPolicy.RUNTIME
  4. @Qualifier
  5. @Retention(RUNTIME)
  6. @interface V8 {
  7. }
  1. import javax.inject.Qualifier
  2. import java.lang.annotation.Retention
  3. import java.lang.annotation.RetentionPolicy.RUNTIME
  4. @Qualifier
  5. @Retention(RUNTIME)
  6. annotation class V8

The above annotation is itself annotated with the @Qualifier annotation to designate it as a qualifier. You can then use the annotation at any injection point in your code. For example:

  1. @Inject Vehicle(@V8 Engine engine) {
  2. this.engine = engine;
  3. }
  1. @Inject Vehicle(@V8 Engine engine) {
  2. this.engine = engine
  3. }
  1. @Inject constructor(@V8 val engine: Engine) {

Primary and Secondary Beans

Primary is a qualifier that indicates that a bean is the primary bean that should be selected in the case of multiple possible interface implementations.

Consider the following example:

  1. public interface ColorPicker {
  2. String color();
  3. }
  1. interface ColorPicker {
  2. String color()
  3. }
  1. interface ColorPicker {
  2. fun color(): String
  3. }

Given a common interface called ColorPicker that is implemented by multiple classes.

The Primary Bean

  1. import io.micronaut.context.annotation.Primary;
  2. import javax.inject.Singleton;
  3. @Primary
  4. @Singleton
  5. class Green implements ColorPicker {
  6. @Override
  7. public String color() {
  8. return "green";
  9. }
  10. }

The Primary Bean

  1. import io.micronaut.context.annotation.Primary
  2. import javax.inject.Singleton
  3. @Primary
  4. @Singleton
  5. class Green implements ColorPicker {
  6. @Override
  7. String color() {
  8. return "green"
  9. }
  10. }

The Primary Bean

  1. import io.micronaut.context.annotation.Primary
  2. import javax.inject.Singleton
  3. @Primary
  4. @Singleton
  5. class Green: ColorPicker {
  6. override fun color(): String {
  7. return "green"
  8. }
  9. }

The Green bean is a ColorPicker, but is annotated with @Primary.

Another Bean of the Same Type

  1. import javax.inject.Singleton;
  2. @Singleton
  3. public class Blue implements ColorPicker {
  4. @Override
  5. public String color() {
  6. return "blue";
  7. }
  8. }

Another Bean of the Same Type

  1. import javax.inject.Singleton
  2. @Singleton
  3. class Blue implements ColorPicker {
  4. @Override
  5. String color() {
  6. return "blue"
  7. }
  8. }

Another Bean of the Same Type

  1. import javax.inject.Singleton
  2. @Singleton
  3. class Blue: ColorPicker {
  4. override fun color(): String {
  5. return "blue"
  6. }
  7. }

The Blue bean is also a ColorPicker and hence you have two possible candidates when injecting the ColorPicker interface. Since Green is the primary it will always be favoured.

  1. @Controller("/testPrimary")
  2. public class TestController {
  3. protected final ColorPicker colorPicker;
  4. public TestController(ColorPicker colorPicker) { (1)
  5. this.colorPicker = colorPicker;
  6. }
  7. @Get
  8. public String index() {
  9. return colorPicker.color();
  10. }
  11. }
  1. @Controller("/test")
  2. class TestController {
  3. protected final ColorPicker colorPicker
  4. TestController(ColorPicker colorPicker) { (1)
  5. this.colorPicker = colorPicker
  6. }
  7. @Get
  8. String index() {
  9. colorPicker.color()
  10. }
  11. }
  1. @Controller("/test")
  2. class TestController(val colorPicker: ColorPicker) { (1)
  3. @Get
  4. fun index(): String {
  5. return colorPicker.color()
  6. }
  7. }
1Although there are two ColorPicker beans, Green gets injected due to the @Primary annotation.

If multiple possible candidates are present and no @Primary is defined then a NonUniqueBeanException will be thrown.

In addition to @Primary, there is also a Secondary annotation which causes the opposite effect and allows de-prioritizing a bean.