Extension for binding multiple instances in a collection with Set, Map, and Optional binding capabilities.
—
Optional binding functionality that allows frameworks to define injection points that may or may not be bound by users, with support for default values. OptionalBinder enables flexible architecture where frameworks can provide extension points that users can optionally override.
Creates new OptionalBinder instances for different types and keys.
/**
* Returns a new OptionalBinder for the given type.
*/
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Class<T> type);
/**
* Returns a new OptionalBinder for the given TypeLiteral.
*/
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, TypeLiteral<T> type);
/**
* Returns a new OptionalBinder for the given Key (includes annotation).
*/
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Key<T> type);Set default and actual values for the optional binding.
/**
* Returns a binding builder used to set the default value that will be injected.
* The binding set by this method will be ignored if setBinding is called.
*
* It is an error to call this method without also calling one of the to methods
* on the returned binding builder.
*/
public LinkedBindingBuilder<T> setDefault();
/**
* Returns a binding builder used to set the actual value that will be injected.
* This overrides any binding set by setDefault.
*
* It is an error to call this method without also calling one of the to methods
* on the returned binding builder.
*/
public LinkedBindingBuilder<T> setBinding();Contribute values using provider methods with optional-specific annotations.
/**
* Annotates methods of a Module to add items to an OptionalBinder. The method's return
* type and binding annotation determines what Optional this will contribute to.
*/
@Target(METHOD)
@Retention(RUNTIME)
public @interface ProvidesIntoOptional {
enum Type {
/** Corresponds to OptionalBinder.setBinding. */
ACTUAL,
/** Corresponds to OptionalBinder.setDefault. */
DEFAULT
}
/** Specifies if the binding is for the actual or default value. */
Type value();
}Usage Examples:
Basic Optional Binding - Framework Perspective:
// Framework module defines optional extension point
public class FrameworkModule extends AbstractModule {
@Override
protected void configure() {
// Create optional binding for Renamer - users may or may not provide one
OptionalBinder.newOptionalBinder(binder(), Renamer.class);
}
}
// Framework code injects Optional
@Inject
public FileProcessor(Optional<Renamer> renamer) {
this.renamer = renamer;
}
public void processFile(File file) {
if (renamer.isPresent()) {
file = renamer.get().rename(file);
}
// process file...
}User Override - Option 1 (Direct Binding):
public class UserRenamerModule extends AbstractModule {
@Override
protected void configure() {
// User provides implementation via direct binding
bind(Renamer.class).to(ReplacingRenamer.class);
}
}User Override - Option 2 (OptionalBinder):
public class UserRenamerModule extends AbstractModule {
@Override
protected void configure() {
// User provides implementation via OptionalBinder
OptionalBinder.newOptionalBinder(binder(), Renamer.class)
.setBinding().to(ReplacingRenamer.class);
}
}Optional Binding with Default Value:
public class FrameworkModule extends AbstractModule {
@Override
protected void configure() {
OptionalBinder<String> urlBinder = OptionalBinder.newOptionalBinder(
binder(), Key.get(String.class, LookupUrl.class));
urlBinder.setDefault().toInstance("http://default.example.com");
}
}
// Framework injection - will use default if user doesn't override
@Inject
public ServiceClient(@LookupUrl String lookupUrl) {
this.lookupUrl = lookupUrl; // "http://default.example.com" unless overridden
}
// User can override the default
public class UserConfigModule extends AbstractModule {
@Override
protected void configure() {
OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
.setBinding().toInstance("http://custom.example.com");
}
}Provider Method Binding:
public class ConfigModule extends AbstractModule {
@ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
@Named("apiUrl")
String provideDefaultApiUrl() {
return "https://api.example.com";
}
@ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
@Named("apiUrl")
String provideProductionApiUrl(@Named("environment") String env) {
if ("production".equals(env)) {
return "https://prod-api.example.com";
}
return null; // Will cause Optional to be absent
}
}Optional Provider Injection:
// Can inject Optional<Provider<T>> for lazy evaluation
@Inject
public ServiceClient(Optional<Provider<Renamer>> renamerProvider) {
this.renamerProvider = renamerProvider;
}
public void processFile(File file) {
if (renamerProvider.isPresent()) {
Renamer renamer = renamerProvider.get().get(); // Lazy creation
file = renamer.rename(file);
}
}Guava vs Java Optional Support:
// Both Guava and Java Optional are supported for compatibility
import java.util.Optional;
import com.google.common.base.Optional;
public class ServiceA {
@Inject
ServiceA(java.util.Optional<Renamer> renamer) { // Java 8+ Optional
this.renamer = renamer;
}
}
public class ServiceB {
@Inject
ServiceB(com.google.common.base.Optional<Renamer> renamer) { // Guava Optional
this.renamer = renamer;
}
}Annotated Optional Bindings:
// Multiple optionals of same type using annotations
public class DatabaseModule extends AbstractModule {
@Override
protected void configure() {
// Primary database connection (required)
OptionalBinder.newOptionalBinder(binder(),
Key.get(DataSource.class, Names.named("primary")))
.setDefault().to(DefaultDataSource.class);
// Secondary database connection (truly optional)
OptionalBinder.newOptionalBinder(binder(),
Key.get(DataSource.class, Names.named("secondary")));
}
}
@Inject
public DataManager(@Named("primary") Optional<DataSource> primary,
@Named("secondary") Optional<DataSource> secondary) {
this.primary = primary; // Will be present (has default)
this.secondary = secondary; // May be absent
}Null Provider Handling:
public class ConfigModule extends AbstractModule {
@ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
Renamer provideRenamer(@Named("enableRenaming") boolean enabled) {
if (enabled) {
return new FileRenamer();
}
return null; // Returning null makes Optional absent
}
}Complex Optional Scenarios:
// Framework provides multiple extension points
public class PluginFrameworkModule extends AbstractModule {
@Override
protected void configure() {
// Optional authentication plugin
OptionalBinder.newOptionalBinder(binder(), AuthPlugin.class);
// Optional caching plugin with default
OptionalBinder.newOptionalBinder(binder(), CachePlugin.class)
.setDefault().to(NoOpCachePlugin.class);
// Optional custom error handler
OptionalBinder.newOptionalBinder(binder(), ErrorHandler.class);
}
}
@Inject
public PluginManager(Optional<AuthPlugin> auth,
Optional<CachePlugin> cache,
Optional<ErrorHandler> errorHandler) {
this.authPlugin = auth.orElse(null);
this.cachePlugin = cache.get(); // Safe - has default
this.errorHandler = errorHandler.orElse(new DefaultErrorHandler());
}Optional<T> and Optional<Provider<T>> bindingsInstall with Tessl CLI
npx tessl i tessl/maven-com-google-inject-extensions--guice-multibindings