A fast dependency injector for Java and Android that generates plain Java source code without using reflection or runtime bytecode generation
—
Multibindings in Dagger allow multiple modules to contribute elements to the same collection (Set or Map), enabling modular composition of related dependencies. This pattern is particularly useful for plugin systems, event handlers, and configurable services.
Method return type contributes individual elements to a Set<T>. The final injected set is immutable and contains all contributed elements.
/**
* Annotates provider methods whose return type contributes to a Set<T>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface IntoSet {}Usage Examples:
@Module
public class ValidationModule {
@Provides
@IntoSet
Validator provideEmailValidator() {
return new EmailValidator();
}
@Provides
@IntoSet
Validator providePhoneValidator() {
return new PhoneValidator();
}
}
@Module
public class AdditionalValidationModule {
@Provides
@IntoSet
Validator provideCreditCardValidator() {
return new CreditCardValidator();
}
}
// Injection
public class ValidationService {
private final Set<Validator> validators;
@Inject
public ValidationService(Set<Validator> validators) {
this.validators = validators; // Contains all 3 validators
}
}Method return type is Set<T>, and all elements from the returned set are contributed to the final set. Useful for providing default empty sets or bulk contributions.
/**
* Annotates provider methods whose return type is Set<T>, contributing all elements
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ElementsIntoSet {}Usage Examples:
@Module
public class PluginModule {
@Provides
@ElementsIntoSet
Set<Plugin> provideDefaultPlugins() {
return ImmutableSet.of(
new LoggingPlugin(),
new MetricsPlugin(),
new CachePlugin()
);
}
@Provides
@ElementsIntoSet
Set<Plugin> provideConditionalPlugins() {
if (BuildConfig.DEBUG) {
return ImmutableSet.of(new DebugPlugin());
}
return Collections.emptySet();
}
}Declares that a multibinding exists, ensuring an empty collection is available when no contributions are made. Required for sets/maps that may be empty.
/**
* Annotates abstract methods that declare multibindings exist
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Multibinds {}Usage Examples:
@Module
public abstract class PluginDeclarationModule {
// Ensures Set<Plugin> is available even if no plugins contribute
@Multibinds
abstract Set<Plugin> declarePluginSet();
// Ensures Map<String, Service> is available even if empty
@Multibinds
abstract Map<String, Service> declareServiceMap();
}Method return type contributes to a Map<K, Provider<V>>. Must be combined with a @MapKey-annotated annotation to specify the key.
/**
* Annotates provider methods whose return type contributes to a Map<K, Provider<V>>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface IntoMap {}Usage Examples:
@Module
public class ServiceModule {
@Provides
@IntoMap
@StringKey("user")
Service provideUserService() {
return new UserService();
}
@Provides
@IntoMap
@StringKey("order")
Service provideOrderService() {
return new OrderService();
}
}
// Injection - note Provider<V> values
public class ServiceRegistry {
private final Map<String, Provider<Service>> services;
@Inject
public ServiceRegistry(Map<String, Provider<Service>> services) {
this.services = services;
}
public Service getService(String name) {
Provider<Service> provider = services.get(name);
return provider != null ? provider.get() : null;
}
}Identifies annotation types used to associate keys with values for map multibindings.
/**
* Meta-annotation for creating map key annotations
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MapKey {
/**
* If true, use the annotation member value as the key
* If false, use the entire annotation instance as the key
*/
boolean unwrapValue() default true;
}@StringKey:
/**
* MapKey annotation for String keys
*/
@MapKey
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface StringKey {
String value();
}@IntKey:
/**
* MapKey annotation for int keys
*/
@MapKey
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface IntKey {
int value();
}@LongKey:
/**
* MapKey annotation for long keys
*/
@MapKey
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface LongKey {
long value();
}@ClassKey:
/**
* MapKey annotation for Class<?> keys
*/
@MapKey
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ClassKey {
Class<?> value();
}@LazyClassKey:
/**
* MapKey annotation for Class<?> keys with lazy loading to prevent class loading
*/
@MapKey(unwrapValue = false)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface LazyClassKey {
Class<?> value();
}You can create custom map key annotations for complex keys:
// Simple custom key (unwrapValue = true)
@MapKey
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnvironmentKey {
Environment value();
}
// Complex custom key (unwrapValue = false)
@MapKey(unwrapValue = false)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceKey {
String name();
int version();
Environment environment();
}
// Usage
@Module
public class ComplexKeyModule {
@Provides
@IntoMap
@ServiceKey(name = "user", version = 2, environment = Environment.PROD)
Service provideUserServiceV2() {
return new UserServiceV2();
}
}Both @IntoSet and @IntoMap work with @Binds for more efficient delegation:
@Module
public abstract class ValidatorBindingModule {
@Binds
@IntoSet
abstract Validator bindEmailValidator(EmailValidator impl);
@Binds
@IntoMap
@StringKey("email")
abstract Validator bindEmailValidatorToMap(EmailValidator impl);
}Multibindings support qualifiers to create separate collections:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Internal {}
@Module
public class QualifiedMultibindingModule {
@Provides
@IntoSet
@Internal
Service provideInternalService() {
return new InternalService();
}
@Provides
@IntoSet
Service providePublicService() {
return new PublicService();
}
}
// Different sets injected based on qualifier
public class ServiceManager {
@Inject
public ServiceManager(
Set<Service> publicServices,
@Internal Set<Service> internalServices
) {
// Two separate sets
}
}Declare Empty Collections:
@Module
public abstract class PluginDeclarations {
@Multibinds abstract Set<Plugin> plugins();
@Multibinds abstract Map<String, Handler> handlers();
}Use Appropriate Collection Types:
// For individual contributions
@IntoSet
Validator provideValidator() { /* ... */ }
// For bulk contributions
@ElementsIntoSet
Set<Validator> provideValidators() { /* ... */ }Consider Map vs Set:
// Use Set for collections of similar items
Set<Validator> validators;
// Use Map for keyed lookups
Map<String, Provider<Service>> servicesByName;Install with Tessl CLI
npx tessl i tessl/maven-com-google-dagger--dagger