0
# Result Type for Error Handling
1
2
Monadic error handling similar to Rust's Result type, providing safe and composable error management without exceptions. This approach enables functional error handling patterns that make error states explicit and composable.
3
4
## Capabilities
5
6
### Result Creation
7
8
Create Result instances representing successful values or error states.
9
10
```java { .api }
11
/**
12
* Monadic error handling for success/failure operations
13
* @param <V> The value type for successful results
14
*/
15
public interface Result<V> {
16
/**
17
* Create a successful Result containing the given value
18
* @param value The success value (may be null)
19
* @return Result representing success
20
*/
21
static <T> Result<T> ok(T value);
22
23
/**
24
* Create an error Result with the given error message
25
* @param error Error message describing the failure
26
* @return Result representing failure
27
*/
28
static <T> Result<T> err(CharSequence error);
29
30
/**
31
* Create an error Result with formatted error message
32
* @param format Format string for the error message
33
* @param args Arguments for the format string
34
* @return Result representing failure
35
*/
36
static <T> Result<T> err(String format, Object... args);
37
38
/**
39
* Create a Result with value and optional error
40
* If error is null or empty, creates success; otherwise creates error
41
* @param value The value (used only if no error)
42
* @param error Optional error message
43
* @return Result representing success or failure based on error presence
44
*/
45
static <T> Result<T> of(T value, CharSequence error);
46
}
47
```
48
49
**Usage Example:**
50
51
```java
52
import aQute.bnd.result.Result;
53
54
// Create successful results
55
Result<String> success = Result.ok("Hello, World!");
56
Result<Integer> number = Result.ok(42);
57
Result<String> nullValue = Result.ok(null); // null is allowed
58
59
// Create error results
60
Result<String> error = Result.err("File not found");
61
Result<Integer> formattedError = Result.err("Invalid number: %d", -1);
62
63
// Conditional result creation
64
Result<String> conditional = Result.of("value", someCondition ? "error occurred" : null);
65
```
66
67
### Result Inspection
68
69
Check the state of Result instances and extract values or errors.
70
71
```java { .api }
72
/**
73
* Check if this Result represents a successful value
74
* @return true if this Result contains a value, false if it contains an error
75
*/
76
boolean isOk();
77
78
/**
79
* Check if this Result represents an error
80
* @return true if this Result contains an error, false if it contains a value
81
*/
82
boolean isErr();
83
84
/**
85
* Get the success value as an Optional
86
* @return Optional containing the success value, or empty if this is an error
87
*/
88
Optional<V> value();
89
90
/**
91
* Get the error message as an Optional
92
* @return Optional containing the error message, or empty if this is a success
93
*/
94
Optional<String> error();
95
96
/**
97
* Get the success value, throwing an exception if this is an error
98
* @return The success value
99
* @throws ResultException if this Result represents an error
100
*/
101
V unwrap();
102
103
/**
104
* Get the success value, throwing an exception with custom message if error
105
* @param message Custom message for the exception
106
* @return The success value
107
* @throws ResultException if this Result represents an error
108
*/
109
V unwrap(CharSequence message);
110
```
111
112
**Usage Example:**
113
114
```java
115
Result<String> result = processFile("data.txt");
116
117
// Check result state
118
if (result.isOk()) {
119
result.value().ifPresent(content ->
120
System.out.println("File content: " + content));
121
} else {
122
result.error().ifPresent(errorMsg ->
123
System.err.println("Error: " + errorMsg));
124
}
125
126
// Unwrap with error handling
127
try {
128
String content = result.unwrap();
129
processContent(content);
130
} catch (ResultException e) {
131
handleError(e.getMessage());
132
}
133
134
// Unwrap with custom error message
135
try {
136
String content = result.unwrap("Failed to read configuration file");
137
useConfiguration(content);
138
} catch (ResultException e) {
139
System.err.println(e.getMessage());
140
}
141
```
142
143
### Fallback Operations
144
145
Provide default values or alternative computations for error cases.
146
147
```java { .api }
148
/**
149
* Return the value if Ok, otherwise return the given default value
150
* @param defaultValue Value to return for error cases
151
* @return The success value or the default value
152
*/
153
V orElse(V defaultValue);
154
155
/**
156
* Return the value if Ok, otherwise compute a value using the given supplier
157
* @param supplier Supplier to compute fallback value
158
* @return The success value or computed fallback value
159
*/
160
V orElseGet(SupplierWithException<? extends V> supplier);
161
162
/**
163
* Return the value if Ok, otherwise throw an exception created by the given function
164
* @param exceptionMapper Function to create exception from error message
165
* @return The success value
166
* @throws R if this Result represents an error
167
*/
168
<R extends Throwable> V orElseThrow(FunctionWithException<? super String, ? extends R> exceptionMapper) throws R;
169
```
170
171
**Usage Example:**
172
173
```java
174
Result<String> result = loadConfiguration();
175
176
// Provide default value
177
String config = result.orElse("default-config");
178
179
// Compute fallback value
180
String config2 = result.orElseGet(() -> {
181
System.out.println("Loading default configuration...");
182
return loadDefaultConfiguration();
183
});
184
185
// Throw custom exception
186
try {
187
String config3 = result.orElseThrow(msg -> new ConfigurationException("Config error: " + msg));
188
} catch (ConfigurationException e) {
189
handleConfigError(e);
190
}
191
```
192
193
### Transformation Operations
194
195
Transform successful values while preserving error states.
196
197
```java { .api }
198
/**
199
* Transform the value if Ok, preserving error state
200
* @param mapper Function to transform the success value
201
* @return Result with transformed value or original error
202
*/
203
<U> Result<U> map(FunctionWithException<? super V, ? extends U> mapper);
204
205
/**
206
* Transform the error message if Err, preserving success state
207
* @param mapper Function to transform the error message
208
* @return Result with original value or transformed error
209
*/
210
Result<V> mapErr(FunctionWithException<? super String, ? extends CharSequence> mapper);
211
212
/**
213
* FlatMap operation for chaining Result computations
214
* @param mapper Function that returns another Result
215
* @return Flattened Result from the mapper or original error
216
*/
217
<U> Result<U> flatMap(FunctionWithException<? super V, ? extends Result<? extends U>> mapper);
218
```
219
220
**Usage Example:**
221
222
```java
223
Result<String> fileContent = readFile("data.txt");
224
225
// Transform successful values
226
Result<Integer> lineCount = fileContent.map(content -> content.split("\n").length);
227
Result<String> upperCase = fileContent.map(String::toUpperCase);
228
229
// Transform error messages
230
Result<String> friendlyError = fileContent.mapErr(err -> "Unable to process file: " + err);
231
232
// Chain operations with flatMap
233
Result<ProcessedData> processed = fileContent
234
.flatMap(content -> parseData(content))
235
.flatMap(data -> validateData(data))
236
.flatMap(data -> transformData(data));
237
238
// Complex pipeline
239
Result<String> result = Result.ok("hello")
240
.map(String::toUpperCase) // Result.ok("HELLO")
241
.map(s -> s + " WORLD") // Result.ok("HELLO WORLD")
242
.flatMap(s -> s.length() > 5 ?
243
Result.ok(s) :
244
Result.err("String too short")); // Result.ok("HELLO WORLD")
245
```
246
247
### Recovery Operations
248
249
Recover from error states by providing alternative computations or transformations.
250
251
```java { .api }
252
/**
253
* Recover from error by transforming error message to success value
254
* @param recovery Function to transform error message to success value
255
* @return Result with recovered value or original success value
256
*/
257
Result<V> recover(FunctionWithException<? super String, ? extends V> recovery);
258
259
/**
260
* Recover from error by providing alternative Result computation
261
* @param recovery Function to transform error message to alternative Result
262
* @return Alternative Result or original success Result
263
*/
264
Result<V> recoverWith(FunctionWithException<? super String, ? extends Result<? extends V>> recovery);
265
```
266
267
**Usage Example:**
268
269
```java
270
Result<String> primary = loadPrimaryConfig();
271
272
// Recover with default value
273
Result<String> recovered = primary.recover(errorMsg -> {
274
System.out.println("Primary config failed: " + errorMsg);
275
return "default-configuration";
276
});
277
278
// Recover with alternative computation
279
Result<String> withFallback = primary.recoverWith(errorMsg -> {
280
System.out.println("Trying fallback config due to: " + errorMsg);
281
return loadFallbackConfig();
282
});
283
284
// Multiple recovery attempts
285
Result<DatabaseConnection> connection = connectToPrimary()
286
.recoverWith(err -> connectToSecondary())
287
.recoverWith(err -> connectToLocal())
288
.recover(err -> createMockConnection());
289
```
290
291
### Consumer Operations
292
293
Execute side effects based on Result state without transforming the Result.
294
295
```java { .api }
296
/**
297
* Execute consumers based on Result state
298
* @param onSuccess Consumer to execute if Result is Ok
299
* @param onError Consumer to execute if Result is Err
300
*/
301
void accept(ConsumerWithException<? super V> onSuccess, ConsumerWithException<? super String> onError);
302
303
/**
304
* Convert to error Result regardless of current state (for testing/debugging)
305
* @return Error Result with current error message or "Expected error but was Ok"
306
*/
307
Result<V> asError();
308
```
309
310
**Usage Example:**
311
312
```java
313
Result<String> result = processRequest();
314
315
// Handle both success and error cases
316
result.accept(
317
value -> System.out.println("Success: " + value),
318
error -> System.err.println("Error: " + error)
319
);
320
321
// Logging example
322
result.accept(
323
value -> logger.info("Processed successfully: {}", value),
324
error -> logger.error("Processing failed: {}", error)
325
);
326
```
327
328
### Exception Handling
329
330
The Result type uses specific exception types for error propagation.
331
332
```java { .api }
333
/**
334
* Exception thrown by unwrap operations when Result contains an error
335
*/
336
public class ResultException extends RuntimeException {
337
/**
338
* Create ResultException with error message
339
* @param message Error message from the Result
340
*/
341
public ResultException(String message);
342
343
/**
344
* Create ResultException with error message and cause
345
* @param message Error message from the Result
346
* @param cause Underlying cause
347
*/
348
public ResultException(String message, Throwable cause);
349
}
350
```
351
352
## Functional Interfaces with Exception Support
353
354
The Result API uses specialized functional interfaces that support checked exceptions:
355
356
```java { .api }
357
@FunctionalInterface
358
public interface SupplierWithException<T> {
359
T get() throws Exception;
360
}
361
362
@FunctionalInterface
363
public interface FunctionWithException<T, R> {
364
R apply(T value) throws Exception;
365
}
366
367
@FunctionalInterface
368
public interface ConsumerWithException<T> {
369
void accept(T value) throws Exception;
370
}
371
```
372
373
## Pattern Examples
374
375
### Configuration Loading with Fallbacks
376
377
```java
378
public Result<Configuration> loadConfiguration() {
379
return loadFromFile("app.config")
380
.recoverWith(err -> loadFromClasspath("default.config"))
381
.recoverWith(err -> Result.ok(Configuration.createDefault()))
382
.map(this::validateConfiguration)
383
.flatMap(config -> config.isValid() ?
384
Result.ok(config) :
385
Result.err("Configuration validation failed"));
386
}
387
```
388
389
### Safe Resource Processing
390
391
```java
392
public Result<ProcessedData> processFile(String filename) {
393
return readFile(filename)
394
.flatMap(this::parseContent)
395
.flatMap(this::validateContent)
396
.map(this::processContent)
397
.mapErr(err -> "Failed to process " + filename + ": " + err);
398
}
399
400
public void handleFileProcessing(String filename) {
401
processFile(filename).accept(
402
data -> saveProcessedData(data),
403
error -> logError(error)
404
);
405
}
406
```
407
408
### API Call Chain
409
410
```java
411
public Result<UserProfile> getUserProfile(String userId) {
412
return validateUserId(userId)
413
.flatMap(id -> fetchUser(id))
414
.flatMap(user -> fetchUserPermissions(user.getId())
415
.map(permissions -> new UserProfile(user, permissions)))
416
.recover(error -> createGuestProfile());
417
}
418
```
419
420
## Comparison with Optional and Exceptions
421
422
**vs Optional:**
423
- Result handles both success values and error information
424
- Optional only represents presence/absence, not why something is absent
425
- Result provides error context for debugging and user feedback
426
427
**vs Exceptions:**
428
- Result makes error states explicit in method signatures
429
- Exceptions require try-catch blocks and can be forgotten
430
- Result enables functional composition of error-prone operations
431
- Result provides better performance for expected error conditions
432
433
## Best Practices
434
435
1. **Use for Expected Errors**: Use Result for operations that commonly fail (file I/O, network calls, parsing)
436
2. **Chain Operations**: Use flatMap to chain multiple fallible operations
437
3. **Provide Context**: Include descriptive error messages
438
4. **Handle Both Cases**: Always handle both success and error cases explicitly
439
5. **Recovery Strategies**: Use recover/recoverWith for graceful degradation
440
6. **Avoid Unwrap**: Prefer orElse/orElseGet over unwrap to avoid exceptions