Dagger is a compile-time dependency injection framework for Java and Android that generates code to perform dependency injection. This comprehensive guide covers Dagger 2 usage, components, modules, scopes, and best practices.
Basic Dagger Setup
1. Dependencies Configuration
Maven:
<properties> <dagger.version>2.48.1</dagger.version> </properties> <dependencies> <dependency> <groupId>com.google.dagger</groupId> <artifactId>dagger</artifactId> <version>${dagger.version}</version> </dependency> <dependency> <groupId>com.google.dagger</groupId> <artifactId>dagger-compiler</artifactId> <version>${dagger.version}</version> <scope>provided</scope> </dependency> </dependencies> Gradle:
dependencies { implementation 'com.google.dagger:dagger:2.48.1' annotationProcessor 'com.google.dagger:dagger-compiler:2.48.1' } 2. Basic Dependency Injection
Simple Classes:
// Domain model public class User { private final String name; private final String email; public User(String name, String email) { this.name = name; this.email = email; } // Getters public String getName() { return name; } public String getEmail() { return email; } } // Repository interface public interface UserRepository { User findById(Long id); void save(User user); } // Repository implementation public class UserRepositoryImpl implements UserRepository { @Override public User findById(Long id) { // Implementation to fetch user from database return new User("John Doe", "[email protected]"); } @Override public void save(User user) { // Implementation to save user to database System.out.println("Saving user: " + user.getName()); } } // Service class that depends on repository public class UserService { private final UserRepository userRepository; @Inject public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User getUser(Long id) { return userRepository.findById(id); } public void createUser(String name, String email) { User user = new User(name, email); userRepository.save(user); } } Dagger Modules and Components
3. Basic Module and Component
AppModule.java:
import dagger.Module; import dagger.Provides; import javax.inject.Singleton; @Module public class AppModule { @Provides @Singleton public UserRepository provideUserRepository() { return new UserRepositoryImpl(); } @Provides @Singleton public UserService provideUserService(UserRepository userRepository) { return new UserService(userRepository); } } AppComponent.java:
import dagger.Component; import javax.inject.Singleton; @Singleton @Component(modules = {AppModule.class}) public interface AppComponent { UserService getUserService(); UserRepository getUserRepository(); // Factory method to create instances static AppComponent create() { return DaggerAppComponent.create(); } } 4. Using Dagger in Application
MainApplication.java:
public class MainApplication { private static AppComponent appComponent; public static void main(String[] args) { // Initialize Dagger component appComponent = AppComponent.create(); // Use injected dependencies UserService userService = appComponent.getUserService(); User user = userService.getUser(1L); System.out.println("User: " + user.getName() + " - " + user.getEmail()); userService.createUser("Jane Smith", "[email protected]"); } public static AppComponent getAppComponent() { return appComponent; } } Advanced Dagger Features
5. Multiple Modules and Component Dependencies
DatabaseModule.java:
@Module public class DatabaseModule { @Provides @Singleton public DataSource provideDataSource() { // Configure and return DataSource HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb"); dataSource.setUsername("user"); dataSource.setPassword("password"); return dataSource; } @Provides @Singleton public JdbcTemplate provideJdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } } NetworkModule.java:
@Module public class NetworkModule { private final String baseUrl; public NetworkModule(String baseUrl) { this.baseUrl = baseUrl; } @Provides @Singleton public OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); } @Provides @Singleton public Retrofit provideRetrofit(OkHttpClient okHttpClient) { return new Retrofit.Builder() .baseUrl(baseUrl) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton public UserApi provideUserApi(Retrofit retrofit) { return retrofit.create(UserApi.class); } } Advanced AppComponent.java:
@Singleton @Component(modules = { AppModule.class, DatabaseModule.class, NetworkModule.class }) public interface AppComponent { UserService getUserService(); DataSource getDataSource(); UserApi getUserApi(); // Component builder for modules with constructor parameters @Component.Builder interface Builder { Builder networkModule(NetworkModule networkModule); Builder appModule(AppModule appModule); Builder databaseModule(DatabaseModule databaseModule); AppComponent build(); } static AppComponent create() { return DaggerAppComponent.builder() .appModule(new AppModule()) .databaseModule(new DatabaseModule()) .networkModule(new NetworkModule("https://api.example.com/")) .build(); } } 6. @Binds and Abstract Modules
ServiceModule.java:
@Module public abstract class ServiceModule { @Binds public abstract UserRepository bindUserRepository(UserRepositoryImpl impl); @Binds @Singleton public abstract NotificationService bindNotificationService( EmailNotificationService impl); } // Interface and implementation public interface NotificationService { void sendNotification(String message, String recipient); } public class EmailNotificationService implements NotificationService { @Inject public EmailNotificationService() {} @Override public void sendNotification(String message, String recipient) { System.out.println("Sending email to " + recipient + ": " + message); } } Scopes and Custom Scopes
7. Custom Scopes
Custom Scopes:
import javax.inject.Scope; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Scope @Documented @Retention(RetentionPolicy.RUNTIME) public @interface ApplicationScope {} @Scope @Documented @Retention(RetentionPolicy.RUNTIME) public @interface RequestScope {} @Scope @Documented @Retention(RetentionPolicy.RUNTIME) public @interface SessionScope {} Scoped Modules:
@Module public class ScopedModule { @Provides @ApplicationScope public ApplicationConfig provideApplicationConfig() { return new ApplicationConfig(); } @Provides @RequestScope public RequestContext provideRequestContext() { return new RequestContext(); } @Provides @SessionScope public UserSession provideUserSession() { return new UserSession(); } } Constructor Injection vs Field Injection
8. Different Injection Methods
Constructor Injection (Recommended):
public class OrderService { private final OrderRepository orderRepository; private final PaymentService paymentService; private final NotificationService notificationService; @Inject public OrderService(OrderRepository orderRepository, PaymentService paymentService, NotificationService notificationService) { this.orderRepository = orderRepository; this.paymentService = paymentService; this.notificationService = notificationService; } } Field Injection:
public class ShoppingCartController { @Inject private ShoppingCartService shoppingCartService; @Inject private ProductService productService; @Inject private UserService userService; // Must be called after instance creation to inject fields @Inject public void setupDependencies() { // Optional setup method called after field injection } } Method Injection:
public class ReportGenerator { private DataFormatter dataFormatter; private TemplateEngine templateEngine; @Inject public void setDependencies(DataFormatter dataFormatter, TemplateEngine templateEngine) { this.dataFormatter = dataFormatter; this.templateEngine = templateEngine; } } Qualified Dependencies
9. Using @Named and @Qualifier
Custom Qualifiers:
import javax.inject.Qualifier; @Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface DatabaseUrl {} @Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface ApiKey {} @Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface Production {} @Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface Development {} Qualified Providers:
@Module public class ConfigurationModule { @Provides @DatabaseUrl public String provideDatabaseUrl() { return "jdbc:mysql://localhost:3306/production"; } @Provides @ApiKey public String provideApiKey() { return System.getenv("API_KEY"); } @Provides @Production public DataSource provideProductionDataSource(@DatabaseUrl String url) { return createDataSource(url, "prod_user", "prod_pass"); } @Provides @Development public DataSource provideDevelopmentDataSource() { return createDataSource("jdbc:h2:mem:testdb", "sa", ""); } private DataSource createDataSource(String url, String user, String password) { HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl(url); ds.setUsername(user); ds.setPassword(password); return ds; } } Using Qualified Dependencies:
public class ApplicationService { private final DataSource productionDataSource; private final DataSource developmentDataSource; private final String apiKey; @Inject public ApplicationService(@Production DataSource productionDataSource, @Development DataSource developmentDataSource, @ApiKey String apiKey) { this.productionDataSource = productionDataSource; this.developmentDataSource = developmentDataSource; this.apiKey = apiKey; } } Component Dependencies and Subcomponents
10. Subcomponents for Scoped Dependencies
RequestComponent.java:
@RequestScope @Subcomponent(modules = RequestModule.class) public interface RequestComponent { // Factory method to create instances @Subcomponent.Factory interface Factory { RequestComponent create(); } // Methods to expose dependencies RequestContext getRequestContext(); UserService getUserService(); OrderService getOrderService(); // Inject into classes void inject(OrderController orderController); } RequestModule.java:
@Module public class RequestModule { @Provides @RequestScope public RequestContext provideRequestContext() { return new RequestContext(); } @Provides @RequestScope public OrderService provideOrderService(UserService userService, PaymentService paymentService) { return new OrderService(userService, paymentService); } } Main Application Component:
@Singleton @Component(modules = { AppModule.class, DatabaseModule.class, NetworkModule.class }) public interface ApplicationComponent { // Expose factory for subcomponents RequestComponent.Factory requestComponent(); // Other dependencies... UserService getUserService(); PaymentService getPaymentService(); } Usage in Web Framework:
public class OrderController { private final RequestComponent requestComponent; @Inject public OrderController(ApplicationComponent appComponent) { this.requestComponent = appComponent.requestComponent().create(); this.requestComponent.inject(this); } public void processOrder(Order order) { OrderService orderService = requestComponent.getOrderService(); orderService.process(order); } } Dagger in Web Applications
11. Spring Boot Integration
SpringBootDaggerIntegration.java:
@Configuration public class DaggerConfig { @Bean @Singleton public ApplicationComponent applicationComponent() { return DaggerApplicationComponent.builder() .appModule(new AppModule()) .databaseModule(new DatabaseModule()) .networkModule(new NetworkModule("https://api.example.com")) .build(); } } @Service public class UserService { private final com.example.dagger.UserService daggerUserService; @Inject public UserService(ApplicationComponent applicationComponent) { this.daggerUserService = applicationComponent.getUserService(); } public User findUser(Long id) { return daggerUserService.getUser(id); } } @RestController @RequestMapping("/api/users") public class UserController { private final UserService userService; @Inject public UserController(UserService userService) { this.userService = userService; } @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { User user = userService.findUser(id); return ResponseEntity.ok(user); } } Testing with Dagger
12. Test Modules and Components
TestDatabaseModule.java:
@Module public class TestDatabaseModule { @Provides @Singleton public DataSource provideTestDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("test-schema.sql") .build(); } @Provides @Singleton public UserRepository provideTestUserRepository() { return new MockUserRepository(); } } TestAppComponent.java:
@Singleton @Component(modules = { TestDatabaseModule.class, NetworkModule.class }) public interface TestAppComponent extends AppComponent { @Component.Builder interface Builder { Builder testDatabaseModule(TestDatabaseModule module); Builder networkModule(NetworkModule module); TestAppComponent build(); } } Unit Tests:
public class UserServiceTest { private UserService userService; private TestAppComponent testComponent; @BeforeEach void setUp() { testComponent = DaggerTestAppComponent.builder() .testDatabaseModule(new TestDatabaseModule()) .networkModule(new NetworkModule("http://test.api.com")) .build(); userService = testComponent.getUserService(); } @Test void testGetUser() { User user = userService.getUser(1L); assertNotNull(user); assertEquals("Test User", user.getName()); } } // Mock implementation for testing class MockUserRepository implements UserRepository { @Override public User findById(Long id) { return new User("Test User", "[email protected]"); } @Override public void save(User user) { // Mock implementation } } Advanced Patterns
13. Provider Injection and Lazy Injection
Provider and Lazy Usage:
public class OrderProcessor { private final Provider<OrderService> orderServiceProvider; private final Lazy<ReportService> reportServiceLazy; private final Set<Provider<EventListener>> eventListeners; @Inject public OrderProcessor(Provider<OrderService> orderServiceProvider, Lazy<ReportService> reportServiceLazy, Set<Provider<EventListener>> eventListeners) { this.orderServiceProvider = orderServiceProvider; this.reportServiceLazy = reportServiceLazy; this.eventListeners = eventListeners; } public void processOrder(Order order) { // Get new instance each time OrderService orderService = orderServiceProvider.get(); orderService.process(order); // Get instance only when needed (lazy initialization) ReportService reportService = reportServiceLazy.get(); reportService.generateReport(order); // Multiple implementations eventListeners.forEach(provider -> { EventListener listener = provider.get(); listener.onOrderProcessed(order); }); } } 14. Multibinding for Multiple Implementations
Multibinding Module:
@Module public abstract class EventListenerModule { @Binds @IntoSet public abstract EventListener bindEmailEventListener(EmailEventListener impl); @Binds @IntoSet public abstract EventListener bindSmsEventListener(SmsEventListener impl); @Binds @IntoSet public abstract EventListener bindPushNotificationEventListener( PushNotificationEventListener impl); } // Usage public class EventDispatcher { private final Set<EventListener> eventListeners; @Inject public EventDispatcher(Set<EventListener> eventListeners) { this.eventListeners = eventListeners; } public void dispatchEvent(Event event) { eventListeners.forEach(listener -> listener.handle(event)); } } Best Practices and Common Patterns
15. Dagger Best Practices
Component Organization:
// Good: Organized by feature or layer @Singleton @Component(modules = { DataModule.class, // Database and repository layer ServiceModule.class, // Business logic layer WebModule.class, // Web and API layer ConfigModule.class // Configuration }) public interface ApplicationComponent { // Expose only what's needed UserService getUserService(); OrderService getOrderService(); } // Good: Use @Binds for interface implementations @Module public abstract class ServiceModule { @Binds public abstract UserRepository bindUserRepository(UserRepositoryImpl impl); @Binds public abstract PaymentService bindPaymentService(StripePaymentService impl); } // Good: Scoped dependencies @Module public class SessionModule { @Provides @SessionScope public UserSession provideUserSession() { return new UserSession(); } } Error-Prone Patterns to Avoid:
// ❌ Avoid: Circular dependencies public class ServiceA { @Inject public ServiceA(ServiceB serviceB) { } } public class ServiceB { @Inject public ServiceB(ServiceA serviceA) { } // Circular dependency! } // ✅ Solution: Use Provider or refactor public class ServiceA { @Inject public ServiceA(Provider<ServiceB> serviceBProvider) { } } // ❌ Avoid: Too many constructor parameters public class Service { @Inject public Service(A a, B b, C c, D d, E e, F f, G g) { } // Too many! } // ✅ Solution: Split into smaller services or use factories public class Service { @Inject public Service(ServiceDependencies deps) { } } @Module public class ServiceDependenciesModule { @Provides public ServiceDependencies provideDependencies(A a, B b, C c) { return new ServiceDependencies(a, b, c); } } Key Benefits of Dagger
- Compile-time Safety: Errors are caught at compile time rather than runtime
- Performance: No reflection, generated code is efficient
- Testability: Easy to replace dependencies with test doubles
- Scalability: Handles complex dependency graphs
- Maintainability: Clear dependency structure and relationships
- No Boilerplate: Reduces manual dependency management code
Dagger provides a robust, type-safe dependency injection solution that's particularly valuable in large-scale applications where maintainability and testability are crucial.