Micronaut promotes a clean, modular design by providing powerful annotation-based dependency injection. However, sometimes built-in annotations are not enough. That’s where custom annotations and qualifiers come in.
In this post, we’ll walk through:
- Why you might need custom annotations
- How to create a qualifier annotation
- How to inject beans using your custom annotation
- And how Micronaut processes these behind the scenes.
We’ll use Maven and the package com.kscodes.micronaut.advanced
.

📦 Use Case for Custom Qualifiers
Imagine you have multiple implementations of an interface and want to inject a specific one. Without a custom qualifier, Micronaut can’t decide which to inject.
🛠️ Project Setup (Maven)
1 2 3 4 5 6 7 8 |
<!-- pom.xml --> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-inject</artifactId> </dependency> |
🧩 Step 1: Define the Interface
1 2 3 4 5 6 7 8 9 |
// src/main/java/com/kscodes/micronaut/advanced/GreetingService.java package com.kscodes.micronaut.advanced; public interface GreetingService { String greet(String name); } |
🧪 Step 2: Create Implementations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// EnglishGreetingService.java @English @Singleton public class EnglishGreetingService implements GreetingService { public String greet(String name) { return "Hello, " + name; } } // SpanishGreetingService.java @Spanish @Singleton public class SpanishGreetingService implements GreetingService { public String greet(String name) { return "Hola, " + name; } } |
🏷️ Step 3: Create Custom Annotations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// English.java @Qualifier @Retention(RUNTIME) @Target({ FIELD, PARAMETER, TYPE }) public @interface English {} // Spanish.java @Qualifier @Retention(RUNTIME) @Target({ FIELD, PARAMETER, TYPE }) public @interface Spanish {} |
These custom annotations act as qualifiers, helping Micronaut decide which bean to inject.
📥 Step 4: Injecting Using the Qualifier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Singleton public class GreetingPrinter { private final GreetingService greetingService; public GreetingPrinter(@English GreetingService greetingService) { this.greetingService = greetingService; } public void print(String name) { System.out.println(greetingService.greet(name)); } } |
🧪 Step 5: Running the Application
You can run the main class to see it in action:
1 2 3 4 5 6 7 8 9 10 11 |
public class Application { public static void main(String[] args) { ApplicationContext context = ApplicationContext.run(); GreetingPrinter printer = context.getBean(GreetingPrinter.class); printer.print("Ketan"); context.close(); } } |
Expected Output:
1 2 3 4 |
Hello, Ketan |
To switch to the Spanish greeting, just replace @English
with @Spanish
.
✅ Benefits of Custom Qualifiers
- Clear separation of concerns
- Helps resolve bean ambiguity
- Improves testability and configuration flexibility
📚 External References
🧾 Conclusion
Custom annotations and qualifiers in Micronaut offer a clean way to handle multiple implementations of the same interface. By creating your own qualifiers, you avoid ambiguity and improve the clarity and maintainability of your code.
This pattern is especially useful in real-world applications like:
- Payment providers (Stripe, Razorpay, PayPal)
- Notification services (Email, SMS, WhatsApp)
- Multilingual responses (English, Hindi, Spanish)