0
# Optional Binding (OptionalBinder)
1
2
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.
3
4
## Capabilities
5
6
### OptionalBinder Factory Methods
7
8
Creates new OptionalBinder instances for different types and keys.
9
10
```java { .api }
11
/**
12
* Returns a new OptionalBinder for the given type.
13
*/
14
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Class<T> type);
15
16
/**
17
* Returns a new OptionalBinder for the given TypeLiteral.
18
*/
19
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, TypeLiteral<T> type);
20
21
/**
22
* Returns a new OptionalBinder for the given Key (includes annotation).
23
*/
24
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Key<T> type);
25
```
26
27
### Value Binding
28
29
Set default and actual values for the optional binding.
30
31
```java { .api }
32
/**
33
* Returns a binding builder used to set the default value that will be injected.
34
* The binding set by this method will be ignored if setBinding is called.
35
*
36
* It is an error to call this method without also calling one of the to methods
37
* on the returned binding builder.
38
*/
39
public LinkedBindingBuilder<T> setDefault();
40
41
/**
42
* Returns a binding builder used to set the actual value that will be injected.
43
* This overrides any binding set by setDefault.
44
*
45
* It is an error to call this method without also calling one of the to methods
46
* on the returned binding builder.
47
*/
48
public LinkedBindingBuilder<T> setBinding();
49
```
50
51
### Provider Method Support
52
53
Contribute values using provider methods with optional-specific annotations.
54
55
```java { .api }
56
/**
57
* Annotates methods of a Module to add items to an OptionalBinder. The method's return
58
* type and binding annotation determines what Optional this will contribute to.
59
*/
60
@Target(METHOD)
61
@Retention(RUNTIME)
62
public @interface ProvidesIntoOptional {
63
64
enum Type {
65
/** Corresponds to OptionalBinder.setBinding. */
66
ACTUAL,
67
68
/** Corresponds to OptionalBinder.setDefault. */
69
DEFAULT
70
}
71
72
/** Specifies if the binding is for the actual or default value. */
73
Type value();
74
}
75
```
76
77
**Usage Examples:**
78
79
**Basic Optional Binding - Framework Perspective:**
80
81
```java
82
// Framework module defines optional extension point
83
public class FrameworkModule extends AbstractModule {
84
@Override
85
protected void configure() {
86
// Create optional binding for Renamer - users may or may not provide one
87
OptionalBinder.newOptionalBinder(binder(), Renamer.class);
88
}
89
}
90
91
// Framework code injects Optional
92
@Inject
93
public FileProcessor(Optional<Renamer> renamer) {
94
this.renamer = renamer;
95
}
96
97
public void processFile(File file) {
98
if (renamer.isPresent()) {
99
file = renamer.get().rename(file);
100
}
101
// process file...
102
}
103
```
104
105
**User Override - Option 1 (Direct Binding):**
106
107
```java
108
public class UserRenamerModule extends AbstractModule {
109
@Override
110
protected void configure() {
111
// User provides implementation via direct binding
112
bind(Renamer.class).to(ReplacingRenamer.class);
113
}
114
}
115
```
116
117
**User Override - Option 2 (OptionalBinder):**
118
119
```java
120
public class UserRenamerModule extends AbstractModule {
121
@Override
122
protected void configure() {
123
// User provides implementation via OptionalBinder
124
OptionalBinder.newOptionalBinder(binder(), Renamer.class)
125
.setBinding().to(ReplacingRenamer.class);
126
}
127
}
128
```
129
130
**Optional Binding with Default Value:**
131
132
```java
133
public class FrameworkModule extends AbstractModule {
134
@Override
135
protected void configure() {
136
OptionalBinder<String> urlBinder = OptionalBinder.newOptionalBinder(
137
binder(), Key.get(String.class, LookupUrl.class));
138
urlBinder.setDefault().toInstance("http://default.example.com");
139
}
140
}
141
142
// Framework injection - will use default if user doesn't override
143
@Inject
144
public ServiceClient(@LookupUrl String lookupUrl) {
145
this.lookupUrl = lookupUrl; // "http://default.example.com" unless overridden
146
}
147
148
// User can override the default
149
public class UserConfigModule extends AbstractModule {
150
@Override
151
protected void configure() {
152
OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
153
.setBinding().toInstance("http://custom.example.com");
154
}
155
}
156
```
157
158
**Provider Method Binding:**
159
160
```java
161
public class ConfigModule extends AbstractModule {
162
@ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
163
@Named("apiUrl")
164
String provideDefaultApiUrl() {
165
return "https://api.example.com";
166
}
167
168
@ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
169
@Named("apiUrl")
170
String provideProductionApiUrl(@Named("environment") String env) {
171
if ("production".equals(env)) {
172
return "https://prod-api.example.com";
173
}
174
return null; // Will cause Optional to be absent
175
}
176
}
177
```
178
179
**Optional Provider Injection:**
180
181
```java
182
// Can inject Optional<Provider<T>> for lazy evaluation
183
@Inject
184
public ServiceClient(Optional<Provider<Renamer>> renamerProvider) {
185
this.renamerProvider = renamerProvider;
186
}
187
188
public void processFile(File file) {
189
if (renamerProvider.isPresent()) {
190
Renamer renamer = renamerProvider.get().get(); // Lazy creation
191
file = renamer.rename(file);
192
}
193
}
194
```
195
196
**Guava vs Java Optional Support:**
197
198
```java
199
// Both Guava and Java Optional are supported for compatibility
200
import java.util.Optional;
201
import com.google.common.base.Optional;
202
203
public class ServiceA {
204
@Inject
205
ServiceA(java.util.Optional<Renamer> renamer) { // Java 8+ Optional
206
this.renamer = renamer;
207
}
208
}
209
210
public class ServiceB {
211
@Inject
212
ServiceB(com.google.common.base.Optional<Renamer> renamer) { // Guava Optional
213
this.renamer = renamer;
214
}
215
}
216
```
217
218
**Annotated Optional Bindings:**
219
220
```java
221
// Multiple optionals of same type using annotations
222
public class DatabaseModule extends AbstractModule {
223
@Override
224
protected void configure() {
225
// Primary database connection (required)
226
OptionalBinder.newOptionalBinder(binder(),
227
Key.get(DataSource.class, Names.named("primary")))
228
.setDefault().to(DefaultDataSource.class);
229
230
// Secondary database connection (truly optional)
231
OptionalBinder.newOptionalBinder(binder(),
232
Key.get(DataSource.class, Names.named("secondary")));
233
}
234
}
235
236
@Inject
237
public DataManager(@Named("primary") Optional<DataSource> primary,
238
@Named("secondary") Optional<DataSource> secondary) {
239
this.primary = primary; // Will be present (has default)
240
this.secondary = secondary; // May be absent
241
}
242
```
243
244
**Null Provider Handling:**
245
246
```java
247
public class ConfigModule extends AbstractModule {
248
@ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
249
Renamer provideRenamer(@Named("enableRenaming") boolean enabled) {
250
if (enabled) {
251
return new FileRenamer();
252
}
253
return null; // Returning null makes Optional absent
254
}
255
}
256
```
257
258
**Complex Optional Scenarios:**
259
260
```java
261
// Framework provides multiple extension points
262
public class PluginFrameworkModule extends AbstractModule {
263
@Override
264
protected void configure() {
265
// Optional authentication plugin
266
OptionalBinder.newOptionalBinder(binder(), AuthPlugin.class);
267
268
// Optional caching plugin with default
269
OptionalBinder.newOptionalBinder(binder(), CachePlugin.class)
270
.setDefault().to(NoOpCachePlugin.class);
271
272
// Optional custom error handler
273
OptionalBinder.newOptionalBinder(binder(), ErrorHandler.class);
274
}
275
}
276
277
@Inject
278
public PluginManager(Optional<AuthPlugin> auth,
279
Optional<CachePlugin> cache,
280
Optional<ErrorHandler> errorHandler) {
281
this.authPlugin = auth.orElse(null);
282
this.cachePlugin = cache.get(); // Safe - has default
283
this.errorHandler = errorHandler.orElse(new DefaultErrorHandler());
284
}
285
```
286
287
## Key Features
288
289
- **Optional Injection**: Always provides `Optional<T>` and `Optional<Provider<T>>` bindings
290
- **Default Value Support**: Frameworks can provide defaults that users can override
291
- **Null Provider Support**: Providers returning null result in absent Optional
292
- **Automatic Fallback**: If no explicit binding, tries to use existing user binding of same type
293
- **Both Optional Types**: Supports both Guava and Java Optional for compatibility
294
- **Annotation Support**: Different optionals of same type via binding annotations
295
- **Lazy Evaluation**: Optional providers support lazy instantiation
296
- **Warning**: Default bindings are always created in object graph even when overridden