or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdmap-binding.mdoptional-binding.mdset-binding.md

optional-binding.mddocs/

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