• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

domix / dmx-fun / 25540016301

08 May 2026 06:08AM UTC coverage: 98.387%. Remained the same
25540016301

push

github

web-flow
Merge pull request #397 from domix/feature/adr/004/init

docs(adr): reference ADR-004 in Try javadoc, try guide, and contributing guide

1090 of 1131 branches covered (96.37%)

Branch coverage included in aggregate %.

3059 of 3086 relevant lines covered (99.13%)

5.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.57
core/lib/src/main/java/dmx/fun/Try.java
1
package dmx.fun;
2

3
import java.util.ArrayList;
4
import java.util.Collections;
5
import java.util.List;
6
import java.util.stream.Collector;
7
import java.util.NoSuchElementException;
8
import java.util.Objects;
9
import java.util.Optional;
10
import java.util.function.Consumer;
11
import java.util.function.Function;
12
import java.util.function.Predicate;
13
import java.util.function.Supplier;
14
import java.time.Duration;
15
import java.util.concurrent.CancellationException;
16
import java.util.concurrent.CompletableFuture;
17
import java.util.concurrent.CompletionException;
18
import java.util.concurrent.ExecutionException;
19
import java.util.concurrent.FutureTask;
20
import java.util.concurrent.TimeUnit;
21
import java.util.concurrent.TimeoutException;
22
import java.util.stream.Gatherer;
23
import java.util.stream.Stream;
24
import java.util.stream.StreamSupport;
25
import org.jspecify.annotations.NullMarked;
26
import org.jspecify.annotations.Nullable;
27

28

29
/**
30
 * A monadic type that represents a computation that may either result in a value
31
 * ({@link Success}) or throw an exception ({@link Failure}).
32
 *
33
 * <p>This interface is {@link NullMarked}: all methods return non-null values by
34
 * default. The sole exception is {@link #getOrNull()}, which may return {@code null}
35
 * for two distinct reasons:
36
 * <ol>
37
 *   <li>This instance is a {@link Failure} — no value is present.</li>
38
 *   <li>This instance is a {@link Success} whose value is {@code null}, as produced
39
 *       by {@link #run(CheckedRunnable) Try.run(...)} which yields
40
 *       {@code Success(null)} on a successful void side-effect.</li>
41
 * </ol>
42
 *
43
 * <p>{@code Success(null)} is a valid and intentional state. Callers that need to
44
 * distinguish "succeeded with null" from "failed" should use {@link #isSuccess()},
45
 * {@link #fold(java.util.function.Function, java.util.function.Function) fold()},
46
 * or {@link #get()} rather than {@link #getOrNull()}.
47
 *
48
 * <p>This deliberate asymmetry — {@code Try.Success} accepting {@code null} while
49
 * {@code Result.Ok} rejects it — is documented in
50
 * <a href="https://domix.github.io/dmx-fun/adr/adr-004-null-in-try-vs-result/">
51
 * ADR-004 — Try&lt;V&gt; allows Success(null); Result.Ok rejects null</a>.
52
 *
53
 * @param <Value> the type of the successful value
54
 */
55
@NullMarked
56
public sealed interface Try<Value> permits Try.Success, Try.Failure {
57

58
    /**
59
     * Represents a successful computation result within the Try monad pattern.
60
     * Encapsulates a value of type {@code Value}, indicating the computation succeeded
61
     * without any errors or exceptions.
62
     *
63
     * @param value   the successful result of the computation
64
     * @param <Value> the type of the value being encapsulated
65
     */
66
    record Success<Value>(Value value) implements Try<Value> {
6✔
67
    }
68

69
    /**
70
     * Represents a computational failure within a context that implements the
71
     * {@link Try} interface. This class encapsulates a {@link Throwable} indicating
72
     * the cause of the failure.
73
     *
74
     * @param cause   the exception that caused the failure; must not be null
75
     * @param <Value> the type of the value that would have been produced if the
76
     *                computation had succeeded
77
     */
78
    record Failure<Value>(Throwable cause) implements Try<Value> {
79
        /**
80
         * Compact canonical constructor — validates that {@code cause} is non-null.
81
         *
82
         * @throws NullPointerException if {@code cause} is {@code null}
83
         */
84
        public Failure {
5✔
85
            Objects.requireNonNull(cause, "Failure cause must not be null");
4✔
86
        }
1✔
87
    }
88

89
    /**
90
     * Creates a successful {@code Try} instance containing the provided value.
91
     *
92
     * @param <V>   the type of the value contained in the {@code Try} instance
93
     * @param value the value to be wrapped in a successful {@code Try} instance
94
     * @return a {@code Try} instance representing a successful computation containing the given value
95
     */
96
    static <V> Try<V> success(V value) {
97
        return new Success<>(value);
5✔
98
    }
99

100
    /**
101
     * Creates a {@code Try} instance representing a failure with the given cause.
102
     *
103
     * @param <V>   the type of the value that would have been returned in case of success
104
     * @param cause the throwable that caused the failure; must not be null
105
     * @return a {@code Try} instance representing the failure
106
     */
107
    static <V> Try<V> failure(Throwable cause) {
108
        return new Failure<>(cause);
5✔
109
    }
110

111
    /**
112
     * Creates a {@code Try} instance by executing the given {@code CheckedSupplier}.
113
     * If the supplier executes successfully, a {@code Try} containing the result is returned.
114
     * If an exception is thrown during execution, a {@code Try} containing the throwable is returned.
115
     *
116
     * @param supplier the {@code CheckedSupplier} to execute
117
     * @param <V>      the type of the result supplied by the {@code CheckedSupplier}
118
     * @return a {@code Try} instance representing either a success with the supplied value
119
     * or a failure with the thrown exception
120
     */
121
    static <V> Try<V> of(CheckedSupplier<? extends V> supplier) {
122
        try {
123
            return success(supplier.get());
4✔
124
        } catch (Throwable t) {
1✔
125
            return failure(t);
3✔
126
        }
127
    }
128

129
    /**
130
     * Creates a {@code Try} from a potentially {@code null} value.
131
     *
132
     * <p>If {@code value} is non-null, returns {@code Success(value)}.
133
     * If {@code value} is {@code null}, returns {@code Failure} using the supplied exception —
134
     * the {@code exceptionSupplier} is only called when {@code value} is {@code null}.
135
     *
136
     * <p>This factory is a convenient alternative to the two-step
137
     * {@code Try.fromOptional(Optional.ofNullable(value), exceptionSupplier)} pattern when
138
     * working with APIs that return {@code null} instead of throwing.
139
     *
140
     * <p>Example:
141
     * <pre>{@code
142
     * // Legacy API that returns null on failure
143
     * Try<Connection> t = Try.ofNullable(
144
     *     pool.borrowConnection(),
145
     *     () -> new NoSuchElementException("no connections available")
146
     * );
147
     * }</pre>
148
     *
149
     * @param <V>               the value type
150
     * @param value             the potentially {@code null} value
151
     * @param exceptionSupplier a supplier for the throwable used when {@code value} is {@code null};
152
     *                          must not be {@code null} and must not return {@code null}
153
     * @return {@code Success(value)} if {@code value} is non-null, or
154
     *         {@code Failure(exceptionSupplier.get())} if {@code value} is {@code null}
155
     * @throws NullPointerException if {@code exceptionSupplier} is {@code null} or returns {@code null}
156
     */
157
    static <V> Try<V> ofNullable(@Nullable V value, Supplier<? extends Throwable> exceptionSupplier) {
158
        Objects.requireNonNull(exceptionSupplier, "exceptionSupplier");
4✔
159
        if (value != null) {
2✔
160
            return Try.success(value);
3✔
161
        }
162
        return Try.failure(
3✔
163
            Objects.requireNonNull(exceptionSupplier.get(), "exceptionSupplier returned null")
5✔
164
        );
165
    }
166

167
    /**
168
     * Executes the provided {@code CheckedRunnable} and returns a {@code Try} instance
169
     * representing the outcome of the execution.
170
     *
171
     * <p><b>Note on null value:</b> On success the returned {@code Try<Void>} wraps {@code null}
172
     * as its value (since {@code Void} has no instances). This means that converting the result
173
     * via {@link #toOption()} will always return {@link Option#none()} — use {@link #isSuccess()}
174
     * or {@link #fold(java.util.function.Function, java.util.function.Function) fold()} instead
175
     * to observe the outcome of a {@code run()} call.
176
     *
177
     * @param runnable the {@code CheckedRunnable} to be executed
178
     * @return a {@code Try<Void>} representing the success or failure of the execution.
179
     * On success, its value is {@code null}; on failure, it contains the thrown exception.
180
     */
181
    static Try<Void> run(CheckedRunnable runnable) {
182
        try {
183
            runnable.run();
2✔
184
            return success(null);
3✔
185
        } catch (Throwable t) {
1✔
186
            return failure(t);
3✔
187
        }
188
    }
189

190
    /**
191
     * Executes {@code supplier} on a virtual thread and returns the result as a {@code Try}.
192
     * If the operation does not complete within {@code timeout}, the virtual thread is
193
     * interrupted and a {@code Failure} containing a {@link TimeoutException} is returned.
194
     *
195
     * <p>The {@link TimeoutException} message includes the configured duration in nanoseconds,
196
     * e.g. {@code "Operation timed out after 500000000ns"}. Nanosecond precision is used so
197
     * that sub-millisecond timeouts are honoured without truncation.
198
     *
199
     * <p><b>Requires Java 21+</b> (virtual threads).
200
     *
201
     * <p>Example:
202
     * <pre>{@code
203
     * Try<Response> result = Try.withTimeout(
204
     *     Duration.ofSeconds(5),
205
     *     () -> httpClient.get(url)
206
     * );
207
     * }</pre>
208
     *
209
     * @param <V>      the type of the result supplied
210
     * @param timeout  the maximum time to wait; must not be {@code null}
211
     * @param supplier the computation to run; must not be {@code null}
212
     * @return {@code Success(value)} if the computation completes within the timeout,
213
     *         {@code Failure(TimeoutException)} if the timeout is exceeded,
214
     *         or {@code Failure(cause)} if the computation itself throws before the timeout
215
     * @throws NullPointerException if {@code timeout} or {@code supplier} is {@code null}
216
     */
217
    static <V> Try<V> withTimeout(Duration timeout, CheckedSupplier<? extends V> supplier) {
218
        Objects.requireNonNull(timeout, "timeout");
4✔
219
        Objects.requireNonNull(supplier, "supplier");
4✔
220

221
        FutureTask<V> task = new FutureTask<>(supplier::get);
9✔
222
        Thread thread = Thread.ofVirtual().start(task);
4✔
223

224
        try {
225
            return success(task.get(timeout.toNanos(), TimeUnit.NANOSECONDS));
7✔
226
        } catch (TimeoutException e) {
1✔
227
            thread.interrupt();
2✔
228
            task.cancel(true);
4✔
229
            return failure(new TimeoutException("Operation timed out after " + timeout.toNanos() + "ns"));
8✔
230
        } catch (ExecutionException e) {
1✔
231
            return failure(e.getCause());
4✔
232
        } catch (InterruptedException e) {
1✔
233
            thread.interrupt();
2✔
234
            task.cancel(true);
4✔
235
            Thread.currentThread().interrupt();
2✔
236
            return failure(e);
3✔
237
        } catch (CancellationException e) {
×
238
            return failure(e);
×
239
        }
240
    }
241

242
    /**
243
     * Determines if the current instance represents a successful state.
244
     *
245
     * @return true if the current instance is of type Success, false otherwise
246
     */
247
    default boolean isSuccess() {
248
        return this instanceof Success<?>;
3✔
249
    }
250

251
    /**
252
     * Determines if the current instance represents a failure state.
253
     *
254
     * @return {@code true} if the current instance is of type {@code Failure}, otherwise {@code false}.
255
     */
256
    default boolean isFailure() {
257
        return this instanceof Failure<?>;
3✔
258
    }
259

260
    /**
261
     * Retrieves the value encapsulated in this instance if it represents a success.
262
     * Throws a NoSuchElementException if this instance represents a failure.
263
     *
264
     * @return the value of this instance if it is a success
265
     * @throws NoSuchElementException if this instance is a failure
266
     */
267
    default Value get() {
268
        return switch (this) {
12✔
269
            case Success<Value> s -> s.value();
6✔
270
            case Failure<Value> f -> throw new NoSuchElementException(
7✔
271
                "No value present. Try is a Failure.", f.cause()
3✔
272
            );
273
        };
274
    }
275

276
    /**
277
     * Retrieves the cause of failure if the instance represents a failed state.
278
     * For a successful state, this method throws a NoSuchElementException.
279
     *
280
     * @return the throwable cause of the failure if the instance is a Failure
281
     * @throws NoSuchElementException if the instance is a Success as there is no cause
282
     */
283
    default Throwable getCause() {
284
        return switch (this) {
12✔
285
            case Failure<Value> f -> f.cause();
6✔
286
            case Success<Value> _ -> throw new NoSuchElementException(
5✔
287
                "No cause present. Try is a Success."
288
            );
289
        };
290
    }
291

292
    /**
293
     * Transforms the value held by this instance using the provided mapping function.
294
     * If this instance represents a successful result, the mapping function is applied
295
     * to its value. If this instance represents a failure, the failure is propagated.
296
     *
297
     * @param <NewValue> the type of the resulting value after applying the mapping function
298
     * @param mapper     the function to apply to the value if this instance represents a success
299
     * @return a new instance of {@code Try} containing the mapped value if successful,
300
     * or the original failure if unsuccessful
301
     */
302
    default <NewValue> Try<NewValue> map(Function<? super Value, ? extends NewValue> mapper) {
303
        return switch (this) {
12✔
304
            case Success<Value> s -> {
3✔
305
                try {
306
                    yield success(mapper.apply(s.value()));
8✔
307
                } catch (Throwable t) {
1✔
308
                    yield failure(t);
5✔
309
                }
310
            }
311
            case Failure<Value> f -> failure(f.cause());
8✔
312
        };
313
    }
314

315
    /**
316
     * Applies the provided mapping function to the value contained within this `Try` instance
317
     * if it represents a successful outcome. The mapping function may produce a new `Try` instance
318
     * representing either success or failure. If this instance is already a failure, it
319
     * will return a failure with the same cause.
320
     *
321
     * @param <NewValue> the type of the result contained in the new `Try` instance
322
     * @param mapper     the mapping function to apply to the value, which produces a `Try` instance
323
     * @return a new `Try` instance produced by applying the mapping function to the value,
324
     * or a failure if either this instance is a failure or the function throws an exception
325
     */
326
    default <NewValue> Try<NewValue> flatMap(Function<? super Value, Try<NewValue>> mapper) {
327
        return switch (this) {
12✔
328
            case Success<Value> s -> {
3✔
329
                try {
330
                    yield mapper.apply(s.value());
8✔
331
                } catch (Throwable t) {
1✔
332
                    yield failure(t);
5✔
333
                }
334
            }
335
            case Failure<Value> f -> failure(f.cause());
8✔
336
        };
337
    }
338

339
    /**
340
     * Applies the given mapping function to the cause of a failed {@code Try} instance
341
     * and returns the resulting {@code Try}. If the instance represents success, the value
342
     * is propagated unchanged.
343
     *
344
     * <p>This is the dual of {@link #flatMap}: it operates on the failure channel instead of
345
     * the value channel, allowing recovery from a known failure by running another fallible
346
     * computation. If the mapper itself throws or returns {@code null}, the exception is
347
     * captured and returned as a new {@code Failure}.
348
     *
349
     * @param mapper a function that maps the current cause to a new {@code Try}; must not be {@code null}
350
     * @return the mapped {@code Try} for {@code Failure}, or this instance unchanged for {@code Success}
351
     * @throws NullPointerException if {@code mapper} itself is {@code null}
352
     */
353
    default Try<Value> flatMapError(
354
        Function<? super Throwable, ? extends Try<? extends Value>> mapper
355
    ) {
356
        Objects.requireNonNull(mapper, "mapper");
4✔
357
        return switch (this) {
12✔
358
            case Success<Value> _ -> this;
4✔
359
            case Failure<Value> f -> {
3✔
360
                try {
361
                    Try<? extends Value> mapped = Objects.requireNonNull(
5✔
362
                        mapper.apply(f.cause()),
4✔
363
                        "mapper returned null"
364
                    );
365
                    yield mapped.fold(Try::success, Try::failure);
8✔
366
                } catch (Throwable t) {
1✔
367
                    yield failure(t);
5✔
368
                }
369
            }
370
        };
371
    }
372

373
    /**
374
     * Performs the given action if the current instance represents a successful outcome.
375
     *
376
     * @param action a {@code Consumer} to be executed with the value of a successful outcome
377
     * @return the current {@code Try} instance
378
     */
379
    default Try<Value> onSuccess(Consumer<? super Value> action) {
380
        if (this instanceof Success<Value>(Value value)) {
13✔
381
            action.accept(value);
3✔
382
        }
383
        return this;
2✔
384
    }
385

386
    /**
387
     * Executes a specified action if this instance represents a failure.
388
     *
389
     * @param action the action to be executed, accepting the throwable cause of the failure
390
     * @return the current instance of {@code Try<Value>}
391
     */
392
    default Try<Value> onFailure(Consumer<? super Throwable> action) {
393
        if (this instanceof Failure<Value>(Throwable cause)) {
13✔
394
            action.accept(cause);
3✔
395
        }
396
        return this;
2✔
397
    }
398

399
    /**
400
     * Applies {@code onSuccess} to the value when this is a {@code Success}, or
401
     * {@code onFailure} to the cause when this is a {@code Failure}.
402
     *
403
     * <p>This is the void terminal counterpart to
404
     * {@link #fold(java.util.function.Function, java.util.function.Function) fold} — use it
405
     * when you need to perform side effects on both tracks without producing a return value.
406
     * It forces both branches to be handled explicitly.
407
     *
408
     * <p>Example:
409
     * <pre>{@code
410
     * Try.of(() -> readConfig(path))
411
     *    .match(
412
     *        config  -> applyConfig(config),
413
     *        failure -> log.error("config unavailable", failure)
414
     *    );
415
     * }</pre>
416
     *
417
     * @param onSuccess consumer called with the value when this is a {@code Success};
418
     *                  must not be {@code null}
419
     * @param onFailure consumer called with the cause when this is a {@code Failure};
420
     *                  must not be {@code null}
421
     * @throws NullPointerException if {@code onSuccess} or {@code onFailure} is {@code null}
422
     */
423
    default void match(
424
        Consumer<? super Value> onSuccess,
425
        Consumer<? super Throwable> onFailure
426
    ) {
427
        Objects.requireNonNull(onSuccess, "onSuccess");
4✔
428
        Objects.requireNonNull(onFailure, "onFailure");
4✔
429
        switch (this) {
11✔
430
            case Success<Value> s -> onSuccess.accept(s.value());
8✔
431
            case Failure<Value> f -> onFailure.accept(f.cause());
7✔
432
        }
433
    }
1✔
434

435
    /**
436
     * Recovers from a failure by applying a recovery function to the underlying throwable.
437
     * If the current instance represents a success, it is returned as is.
438
     * Otherwise, the recovery function is applied to the cause of the failure to produce a new success value.
439
     * If the recovery function itself throws an exception, the method returns a new failure wrapping the thrown exception.
440
     *
441
     * @param recoverFn the recovery function to apply in case of failure, accepting the throwable cause of the failure
442
     *                  and producing a new value
443
     * @return a {@code Try} instance representing either the original success, a new success generated by applying
444
     * the recovery function, or a new failure resulting from an exception thrown by the recovery function
445
     */
446
    default Try<Value> recover(Function<? super Throwable, ? extends Value> recoverFn) {
447
        Objects.requireNonNull(recoverFn, "recoverFn");
4✔
448
        return switch (this) {
12✔
449
            case Success<Value> s -> this;
7✔
450
            case Failure<Value> f -> {
3✔
451
                try {
452
                    yield success(recoverFn.apply(f.cause()));
8✔
453
                } catch (Throwable t) {
1✔
454
                    yield failure(t);
5✔
455
                }
456
            }
457
        };
458
    }
459

460
    /**
461
     * Recovers from a failure by applying the given recovery function to the cause of the failure.
462
     *
463
     * @param recoverFn the function that takes a throwable and returns a new {@code Try} instance
464
     *                  for recovery. It is applied only in case of a failure.
465
     * @return the original success instance if this is a success, or the result of the recovery
466
     * function if this is a failure.
467
     */
468
    default Try<Value> recoverWith(Function<? super Throwable, Try<Value>> recoverFn) {
469
        Objects.requireNonNull(recoverFn, "recoverFn");
4✔
470
        return switch (this) {
12✔
471
            case Success<Value> _ -> this;
4✔
472
            case Failure<Value> f -> {
3✔
473
                try {
474
                    Try<Value> recovered = recoverFn.apply(f.cause());
6✔
475
                    yield Objects.requireNonNull(recovered, "recoverFn returned null");
7✔
476
                } catch (Throwable t) {
1✔
477
                    yield failure(t);
5✔
478
                }
479
            }
480
        };
481
    }
482

483
    /**
484
     * Recovers from a failure if — and only if — the exception is an instance of
485
     * {@code exceptionType}, applying the recovery function to produce a new success value.
486
     *
487
     * <p>If this is a {@code Success}, or if the exception does not match
488
     * {@code exceptionType}, this instance is returned unchanged.
489
     * If the recovery function throws, the exception is captured as a new {@code Failure}.
490
     *
491
     * @param <X>           the exception type to match
492
     * @param exceptionType the class of the exception to recover from
493
     * @param recovery      function that maps the matched exception to a recovery value
494
     * @return a {@code Success} with the recovered value, or this instance if the exception
495
     *         did not match or if this is already a {@code Success}
496
     */
497
    default <X extends Throwable> Try<Value> recover(
498
        Class<X> exceptionType,
499
        Function<? super X, ? extends Value> recovery) {
500
        Objects.requireNonNull(exceptionType, "exceptionType");
4✔
501
        Objects.requireNonNull(recovery, "recovery");
4✔
502
        return switch (this) {
12✔
503
            case Success<Value> _ -> this;
4✔
504
            case Failure<Value> f -> {
3✔
505
                if (!exceptionType.isInstance(f.cause())) yield this;
9✔
506
                try {
507
                    yield success(Objects.requireNonNull(
8✔
508
                        recovery.apply(exceptionType.cast(f.cause())), "recovery returned null"));
4✔
509
                } catch (Throwable t) {
1✔
510
                    yield failure(t);
5✔
511
                }
512
            }
513
        };
514
    }
515

516
    /**
517
     * Recovers from a failure if — and only if — the exception is an instance of
518
     * {@code exceptionType}, by applying the recovery function which returns a new
519
     * {@code Try<Value>} for chaining further fallible computations.
520
     *
521
     * <p>If this is a {@code Success}, or if the exception does not match
522
     * {@code exceptionType}, this instance is returned unchanged.
523
     * If the recovery function throws or returns {@code null}, the exception is captured
524
     * as a new {@code Failure}.
525
     *
526
     * @param <X>           the exception type to match
527
     * @param exceptionType the class of the exception to recover from
528
     * @param recovery      function that maps the matched exception to a new {@code Try}
529
     * @return the result of the recovery function, or this instance if the exception
530
     *         did not match or if this is already a {@code Success}
531
     */
532
    @SuppressWarnings("unchecked")
533
    default <X extends Throwable> Try<Value> recoverWith(
534
        Class<X> exceptionType,
535
        Function<? super X, ? extends Try<? extends Value>> recovery) {
536
        Objects.requireNonNull(exceptionType, "exceptionType");
4✔
537
        Objects.requireNonNull(recovery, "recovery");
4✔
538
        return switch (this) {
12✔
539
            case Success<Value> _ -> this;
4✔
540
            case Failure<Value> f -> {
3✔
541
                if (!exceptionType.isInstance(f.cause())) yield this;
9✔
542
                try {
543
                    yield (Try<Value>) Objects.requireNonNull(
8✔
544
                        recovery.apply(exceptionType.cast(f.cause())), "recovery returned null");
5✔
545
                } catch (Throwable t) {
1✔
546
                    yield failure(t);
5✔
547
                }
548
            }
549
        };
550
    }
551

552
    /**
553
     * Returns the value if this instance is a {@code Success}, or the specified fallback value if it is a {@code Failure}.
554
     *
555
     * @param fallback the value to return if this instance is a {@code Failure}.
556
     * @return the value of this instance if it is a {@code Success}, or the specified fallback value if it is a {@code Failure}.
557
     */
558
    default Value getOrElse(Value fallback) {
559
        return this instanceof Success<Value>(Value value) ? value : fallback;
17✔
560
    }
561

562
    /**
563
     * Returns the value of this {@code Try} if it is a {@code Success}, or retrieves a fallback
564
     * value using the provided {@code fallbackSupplier} if it is a {@code Failure}.
565
     *
566
     * @param fallbackSupplier a {@code Supplier} that provides an alternative value to return
567
     *                         if this {@code Try} is a {@code Failure}.
568
     * @return the value of this {@code Try} if it is a {@code Success}, or the value supplied
569
     * by {@code fallbackSupplier} if it is a {@code Failure}.
570
     */
571
    default Value getOrElseGet(Supplier<? extends Value> fallbackSupplier) {
572
        Objects.requireNonNull(fallbackSupplier, "fallbackSupplier");
4✔
573
        return this instanceof Success<Value>(Value value) ? value : fallbackSupplier.get();
18✔
574
    }
575

576
    /**
577
     * Returns the value of this {@code Try} if it is a {@code Success}, or {@code null} if it is a {@code Failure}.
578
     *
579
     * @return the successful value if this {@code Try} is a {@code Success}, or {@code null} if it is a {@code Failure}.
580
     */
581
    default @Nullable Value getOrNull() {
582
        return this instanceof Success<Value>(Value value) ? value : null;
17✔
583
    }
584

585
    /**
586
     * Returns the successful value if this instance is a {@code Success}, or throws an exception
587
     * if this instance is a {@code Failure}.
588
     *
589
     * @return the successful value of this {@code Try} if it is a {@code Success}.
590
     * @throws Exception        if this {@code Try} is a {@code Failure} and the failure cause is an {@code Exception}.
591
     * @throws RuntimeException if this {@code Try} is a {@code Failure} and the failure cause is not an {@code Exception}.
592
     */
593
    default Value getOrThrow() throws Exception {
594
        return switch (this) {
12✔
595
            case Success<Value> s -> s.value();
6✔
596
            case Failure<Value> f -> {
3✔
597
                Throwable cause = f.cause();
3✔
598
                if (cause instanceof Exception exception) {
6✔
599
                    throw exception;
2✔
600
                }
601
                throw new RuntimeException(cause);
5✔
602
            }
603
        };
604
    }
605

606
    /**
607
     * Returns the value if this instance is a Success, or throws the exception
608
     * provided by the exceptionMapper if this instance is a Failure.
609
     *
610
     * @param exceptionMapper a function to map the failure cause (Throwable)
611
     *                        to a RuntimeException that will be thrown.
612
     * @return the value if this is a Success.
613
     * @throws RuntimeException if this is a Failure and the exceptionMapper is invoked.
614
     */
615
    default Value getOrThrow(Function<? super Throwable, ? extends RuntimeException> exceptionMapper) {
616
        return switch (this) {
12✔
617
            case Success<Value> s -> s.value();
6✔
618
            case Failure<Value> f -> throw exceptionMapper.apply(f.cause());
9✔
619
        };
620
    }
621

622
    /**
623
     * Folds the current {@code Try} instance into a single value by applying one of two provided functions.
624
     * If this {@code Try} is a {@code Success}, the {@code onSuccess} function is applied to the value.
625
     * If this {@code Try} is a {@code Failure}, the {@code onFailure} function is applied to the cause.
626
     *
627
     * @param <Folded>  the type of the result after applying one of the functions
628
     * @param onSuccess a function to apply to the value if this {@code Try} is a {@code Success}
629
     * @param onFailure a function to apply to the cause if this {@code Try} is a {@code Failure}
630
     * @return the result of applying the appropriate function based on the state of this {@code Try}
631
     */
632
    default <Folded> Folded fold(
633
        Function<? super Value, ? extends Folded> onSuccess,
634
        Function<? super Throwable, ? extends Folded> onFailure
635
    ) {
636
        return switch (this) {
12✔
637
            case Success<Value> s -> onSuccess.apply(s.value());
8✔
638
            case Failure<Value> f -> onFailure.apply(f.cause());
7✔
639
        };
640
    }
641

642
    /**
643
     * Filters the Try based on the given predicate.
644
     * If this is a Success and the predicate evaluates to true, the result is this Try instance.
645
     * If the predicate evaluates to false, a Failure is returned using an {@link IllegalArgumentException}.
646
     * If this is already a Failure, the result remains unchanged.
647
     *
648
     * <p>On the Success path, if {@code predicate} is {@code null} or throws, a
649
     * {@code Failure(NullPointerException)} or {@code Failure(exception)} is returned rather than
650
     * propagating the error. An existing {@code Failure} is returned unchanged without evaluating
651
     * the predicate, consistent with the Try philosophy of capturing throwables as values.
652
     * See also {@link #filter(Predicate, Supplier)} and {@link #filter(Predicate, java.util.function.Function)}.
653
     *
654
     * @param predicate the condition to evaluate for the value if this is a Success
655
     * @return a Success if the predicate evaluates to true, or a Failure otherwise
656
     */
657
    default Try<Value> filter(Predicate<? super Value> predicate) {
658
        return filter(predicate, () -> new IllegalArgumentException("Predicate does not hold for value"));
10✔
659
    }
660

661
    /**
662
     * Filters the Try based on the given predicate.
663
     * If this is a Success and the predicate evaluates to true, the result is this Try instance.
664
     * If the predicate evaluates to false, a Failure is returned using the provided throwable supplier.
665
     * If this is already a Failure, the result remains unchanged.
666
     *
667
     * <p>On the Success path, if {@code predicate} or {@code throwableSupplier} is {@code null},
668
     * or if either throws, a {@code Failure} wrapping the throwable is returned rather than
669
     * propagating the error. {@code throwableSupplier} is only invoked when the predicate
670
     * evaluates to {@code false}. An existing {@code Failure} is returned unchanged without
671
     * evaluating the predicate or the supplier, consistent with the Try philosophy of capturing
672
     * throwables as values.
673
     * See also {@link #filter(Predicate)} and {@link #filter(Predicate, java.util.function.Function)}.
674
     *
675
     * @param predicate         the condition to evaluate for the value if this is a Success
676
     * @param throwableSupplier a supplier to provide the throwable for the Failure if the predicate evaluates to false
677
     * @return a Success if the predicate evaluates to true, or a Failure otherwise
678
     */
679
    default Try<Value> filter(Predicate<? super Value> predicate, Supplier<? extends Throwable> throwableSupplier) {
680
        return switch (this) {
12✔
681
            case Success<Value> s -> {
3✔
682
                try {
683
                    yield predicate.test(s.value()) ? this : failure(throwableSupplier.get());
14✔
684
                } catch (Throwable t) {
1✔
685
                    yield failure(t);
5✔
686
                }
687
            }
688
            case Failure<Value> _ -> this;
3✔
689
        };
690
    }
691

692
    /**
693
     * Filters this {@code Try} using a predicate and a contextual error function that receives
694
     * the value that failed the test. If this is a {@code Failure}, it is returned unchanged.
695
     * If this is a {@code Success} and the predicate returns {@code false}, a {@code Failure}
696
     * is returned with the throwable produced by {@code errorFn} applied to the value.
697
     *
698
     * <p>Unlike {@link #filter(Predicate, Supplier)}, the error function can produce a message
699
     * that includes the offending value:
700
     * <pre>{@code
701
     * Try.of(() -> parseAge(input))
702
     *    .filter(age -> age >= 0, age -> new IllegalArgumentException("negative age: " + age));
703
     * }</pre>
704
     *
705
     * <p>On the Success path, if {@code predicate} throws, the exception is captured and returned
706
     * as a {@code Failure}; if {@code errorFn} returns {@code null}, a
707
     * {@code Failure(NullPointerException)} is returned rather than propagating the NPE.
708
     * {@code errorFn} is only invoked when the predicate evaluates to {@code false}.
709
     * Note that a {@code null} {@code predicate} or {@code errorFn} is detected eagerly
710
     * and <em>does</em> throw {@code NullPointerException} before any value is tested, regardless
711
     * of whether this instance is a {@code Success} or {@code Failure}.
712
     * See also {@link #filter(Predicate)} and {@link #filter(Predicate, Supplier)}.
713
     *
714
     * @param predicate the condition to test the value; must not be {@code null}
715
     * @param errorFn   a function that produces the throwable when the predicate fails;
716
     *                  must not be {@code null}; if it returns {@code null}, a
717
     *                  {@code Failure(NullPointerException)} is returned
718
     * @return this instance if the predicate holds or this is already a {@code Failure};
719
     *         otherwise a new {@code Failure} produced by {@code errorFn}
720
     * @throws NullPointerException if {@code predicate} or {@code errorFn} is {@code null}
721
     */
722
    default Try<Value> filter(Predicate<? super Value> predicate, Function<? super Value, ? extends Throwable> errorFn) {
723
        Objects.requireNonNull(predicate, "predicate");
4✔
724
        Objects.requireNonNull(errorFn, "errorFn");
4✔
725
        return switch (this) {
12✔
726
            case Success<Value> s -> {
3✔
727
                try {
728
                    if (predicate.test(s.value())) {
5✔
729
                        yield this;
4✔
730
                    }
731
                    yield failure(Objects.requireNonNull(errorFn.apply(s.value()), "errorFn returned null"));
12✔
732
                } catch (Throwable t) {
1✔
733
                    yield failure(t);
5✔
734
                }
735
            }
736
            case Failure<Value> _ -> this;
3✔
737
        };
738
    }
739

740
    /**
741
     * Converts the current {@code Try} instance into a {@code Result} representation.
742
     * If this {@code Try} is a {@code Success}, the resulting {@code Result} will be "ok"
743
     * with the successful value. If this {@code Try} is a {@code Failure}, the resulting
744
     * {@code Result} will be "err" with the failure cause.
745
     *
746
     * @return a {@code Result} instance where the success value is mapped to "ok"
747
     * and the failure cause is mapped to "err".
748
     */
749
    default Result<Value, Throwable> toResult() {
750
        return switch (this) {
12✔
751
            case Success<Value> s -> Result.ok(s.value());
7✔
752
            case Failure<Value> f -> Result.err(f.cause());
6✔
753
        };
754
    }
755

756
    /**
757
     * Converts this {@code Try} into a {@code Result<Value, E>} using a custom error mapper.
758
     * If this is a {@code Success}, returns {@code Ok} with the same value.
759
     * If this is a {@code Failure}, applies {@code errorMapper} to the cause and returns {@code Err}.
760
     *
761
     * <p>This overload is preferred over {@link #toResult()} when a typed error is needed:
762
     * <pre>{@code
763
     * Result<Config, String> r = Try.of(this::loadConfig)
764
     *     .toResult(e -> "load failed: " + e.getMessage());
765
     * }</pre>
766
     *
767
     * @param <E>         the error type of the resulting {@code Result}
768
     * @param errorMapper a function that maps the failure cause to the error value; must not return {@code null}
769
     * @return {@code Ok(value)} on success, or {@code Err(errorMapper.apply(cause))} on failure
770
     * @throws NullPointerException if {@code errorMapper} is {@code null} or returns {@code null}
771
     */
772
    default <E> Result<Value, E> toResult(Function<? super Throwable, ? extends E> errorMapper) {
773
        Objects.requireNonNull(errorMapper, "errorMapper");
4✔
774
        return switch (this) {
12✔
775
            case Success<Value> s -> Result.ok(s.value());
7✔
776
            case Failure<Value> f -> Result.err(
6✔
777
                Objects.requireNonNull(errorMapper.apply(f.cause()), "errorMapper returned null")
4✔
778
            );
779
        };
780
    }
781

782
    /**
783
     * Converts a {@link Result} into a {@link Try} instance.
784
     * If the {@code Result} is successful (contains a value), a successful {@code Try} is returned.
785
     * If the {@code Result} contains an error, a failed {@code Try} is returned with the corresponding cause.
786
     *
787
     * @param <V>    the type of the successful value
788
     * @param result the {@code Result} to convert into a {@code Try}
789
     * @return a {@code Try} representing either the successful value or the failure cause contained in the {@code Result}
790
     */
791
    static <V> Try<V> fromResult(Result<V, ? extends Throwable> result) {
792
        Objects.requireNonNull(result, "result");
4✔
793
        if (result.isOk()) {
3✔
794
            return Try.success(result.get());
4✔
795
        }
796
        return Try.failure(result.getError());
5✔
797
    }
798

799

800
    /**
801
     * Transforms the failure cause using the given function, leaving a {@code Success} unchanged.
802
     * If this is a {@code Failure} and {@code mapper} itself throws, the thrown exception becomes
803
     * the new cause (mirroring the behaviour of {@link #map} on the success channel).
804
     *
805
     * <p>Useful for wrapping low-level exceptions into domain exceptions:
806
     * <pre>{@code
807
     * Try.of(db::query)
808
     *    .mapFailure(e -> new RepositoryException("query failed", e));
809
     * }</pre>
810
     *
811
     * @param mapper a function that maps the current cause to a new {@link Throwable}; must not return {@code null}
812
     * @return this instance if {@code Success}, or a new {@code Failure} with the mapped cause;
813
     *         if {@code mapper} returns {@code null} or throws, the exception is wrapped as a new {@code Failure}
814
     * @throws NullPointerException if {@code mapper} itself is {@code null}
815
     */
816
    default Try<Value> mapFailure(Function<? super Throwable, ? extends Throwable> mapper) {
817
        Objects.requireNonNull(mapper, "mapper");
4✔
818
        return switch (this) {
12✔
819
            case Success<Value> _ -> this;
4✔
820
            case Failure<Value> f -> {
3✔
821
                try {
822
                    yield Try.failure(
6✔
823
                        Objects.requireNonNull(mapper.apply(f.cause()), "mapper returned null")
6✔
824
                    );
825
                } catch (Throwable t) {
1✔
826
                    yield Try.failure(t);
5✔
827
                }
828
            }
829
        };
830
    }
831

832
    /**
833
     * Returns a single-element {@link Stream} containing the success value, or an empty stream on failure.
834
     * Mirrors {@link Option#stream()} for consistency.
835
     *
836
     * <ul>
837
     *   <li>{@code Success(v) → Stream.of(v)}</li>
838
     *   <li>{@code Failure    → Stream.empty()}</li>
839
     * </ul>
840
     *
841
     * @return a stream containing the value if this is a {@code Success}, or an empty stream otherwise
842
     */
843
    default Stream<Value> stream() {
844
        return switch (this) {
12✔
845
            case Success<Value> s -> Stream.of(s.value());
7✔
846
            case Failure<Value> _ -> Stream.empty();
1✔
847
        };
848
    }
849

850
    // ---------- Interoperability: Try <-> Option / Result ----------
851

852
    /**
853
     * Converts the current instance to an Option.
854
     * If the instance is a Success, wraps its value in an Option.
855
     * If the instance is a Failure, returns an empty Option.
856
     *
857
     * @return an Option containing the value if the instance is a Success,
858
     * or an empty Option if the instance is a Failure.
859
     */
860
    default Option<Value> toOption() {
861
        return switch (this) {
12✔
862
            case Success<Value> s -> Option.ofNullable(s.value()); // null => None
7✔
863
            case Failure<Value> _ -> Option.none();
1✔
864
        };
865
    }
866

867
    /**
868
     * Converts this {@code Try} to an {@link Either}.
869
     *
870
     * <p>{@code Success(v)} maps to {@link Either#right(Object)}; {@code Failure(t)} maps to
871
     * {@link Either#left(Object)}. This reflects the natural equivalence between
872
     * {@code Try<V>} and {@code Either<Throwable, V>}.
873
     *
874
     * <p><strong>Note:</strong> unlike {@link #toOption()}, this method does not tolerate a
875
     * {@code null} success value, because {@link Either} enforces non-null values on both
876
     * tracks. If this {@code Try} is a {@code Success} wrapping {@code null} (e.g. a
877
     * {@code Try<Void>}), a {@link NullPointerException} will be thrown.
878
     *
879
     * @return an {@code Either<Throwable, Value>} equivalent of this {@code Try}
880
     * @throws NullPointerException if this is a {@code Success} whose value is {@code null}
881
     */
882
    default Either<Throwable, Value> toEither() {
883
        return switch (this) {
12✔
884
            case Success<Value> s -> Either.right(
6✔
885
                Objects.requireNonNull(s.value(), "Cannot convert Success(null) to Either; Either does not allow null values")
3✔
886
            );
887
            case Failure<Value> f -> Either.left(f.cause());
6✔
888
        };
889
    }
890

891
    /**
892
     * Converts this {@code Try} into an already-completed {@link CompletableFuture}.
893
     *
894
     * <p>If this is a {@code Success}, returns a future completed normally with the value.
895
     * If this is a {@code Failure}, returns a future completed exceptionally with the cause.
896
     *
897
     * @return a completed {@code CompletableFuture<Value>}
898
     */
899
    default CompletableFuture<Value> toFuture() {
900
        return switch (this) {
12✔
901
            case Success<Value> s -> CompletableFuture.completedFuture(s.value());
7✔
902
            case Failure<Value> f -> CompletableFuture.failedFuture(f.cause());
6✔
903
        };
904
    }
905

906
    /**
907
     * Converts an {@link Option} to a {@link Try}. If the {@link Option} contains a value, a successful {@link Try}
908
     * is returned with that value. Otherwise, a failed {@link Try} is created using the supplied exception.
909
     *
910
     * @param <V>               the type of the value contained in the {@link Option}.
911
     * @param opt               the {@link Option} to be converted into a {@link Try}, must not be null.
912
     * @param exceptionSupplier the supplier of the exception to be used when the {@link Option} is empty, must not be null.
913
     * @return a {@link Try} representing either a success containing the value of the {@link Option},
914
     * or a failure containing the exception provided by the supplier.
915
     */
916
    static <V> Try<V> fromOption(Option<? extends V> opt, Supplier<? extends Throwable> exceptionSupplier) {
917
        Objects.requireNonNull(opt, "opt");
4✔
918
        Objects.requireNonNull(exceptionSupplier, "exceptionSupplier");
4✔
919
        return switch (opt) {
12✔
920
            case Option.Some<? extends V> s -> Try.success(s.value());
7✔
921
            case Option.None<? extends V> _ -> Try.failure(
2✔
922
                Objects.requireNonNull(exceptionSupplier.get(), "exceptionSupplier returned null")
5✔
923
            );
924
        };
925
    }
926

927
    /**
928
     * Converts a {@link Optional} into a {@link Try}.
929
     * If the {@code Optional} contains a value, returns {@code Success} with that value.
930
     * If the {@code Optional} is empty, returns {@code Failure} with the exception
931
     * provided by {@code exceptionSupplier}.
932
     *
933
     * @param <V>               the value type
934
     * @param optional          the {@code Optional} to convert; must not be {@code null}
935
     * @param exceptionSupplier a supplier for the throwable to use when the {@code Optional} is empty;
936
     *                          must not be {@code null} and must not return {@code null}
937
     * @return {@code Success(value)} if the {@code Optional} is present,
938
     *         or {@code Failure(exception)} if empty
939
     * @throws NullPointerException if {@code optional} or {@code exceptionSupplier} is {@code null}
940
     */
941
    static <V> Try<V> fromOptional(Optional<? extends V> optional, Supplier<? extends Throwable> exceptionSupplier) {
942
        Objects.requireNonNull(optional, "optional");
4✔
943
        Objects.requireNonNull(exceptionSupplier, "exceptionSupplier");
4✔
944
        return optional.isPresent()
4✔
945
            ? Try.success(optional.get())
4✔
946
            : Try.failure(Objects.requireNonNull(exceptionSupplier.get(), "exceptionSupplier returned null"));
7✔
947
    }
948

949
    // ---------- CompletableFuture interop ----------
950

951
    /**
952
     * Converts a {@link CompletableFuture} into a {@code Try} by blocking until the future
953
     * completes.
954
     *
955
     * <p>If the future completes normally, returns {@code Success(value)}.
956
     * If the future completes exceptionally, the {@link CompletionException} wrapper is
957
     * unwrapped and the original cause is stored as {@code Failure(cause)}.
958
     * If the future was cancelled, returns {@code Failure(CancellationException)}.
959
     *
960
     * @param <V>    the type of the future's value
961
     * @param future the {@code CompletableFuture} to convert; must not be {@code null}
962
     * @return {@code Success(value)} on normal completion, or {@code Failure(cause)} otherwise
963
     * @throws NullPointerException if {@code future} is {@code null}
964
     */
965
    static <V> Try<V> fromFuture(CompletableFuture<? extends V> future) {
966
        Objects.requireNonNull(future, "future must not be null");
4✔
967
        try {
968
            return Try.success(future.join());
4✔
969
        } catch (CompletionException e) {
1✔
970
            Throwable cause = e.getCause();
3✔
971
            return Try.failure(cause != null ? cause : e);
7✔
972
        } catch (CancellationException e) {
1✔
973
            return Try.failure(e);
3✔
974
        }
975
    }
976

977
    /**
978
     * Converts an {@link Either} into a {@code Try}.
979
     *
980
     * <p>{@code Either.right(v)} maps to {@code Try.success(v)};
981
     * {@code Either.left(l)} maps to {@code Try.failure(leftMapper.apply(l))}.
982
     *
983
     * <p>This is the inverse of {@link #toEither()}, completing the bidirectional bridge
984
     * between {@code Try} and {@code Either}.
985
     *
986
     * <p>Example:
987
     * <pre>{@code
988
     * Either<String, Integer> right = Either.right(42);
989
     * Try<Integer> t1 = Try.fromEither(right, IllegalArgumentException::new);
990
     * // Try.success(42)
991
     *
992
     * Either<String, Integer> left = Either.left("not found");
993
     * Try<Integer> t2 = Try.fromEither(left, IllegalArgumentException::new);
994
     * // Try.failure(new IllegalArgumentException("not found"))
995
     * }</pre>
996
     *
997
     * @param <L>        the left (error) type of the {@code Either}
998
     * @param <R>        the right (success) type of the {@code Either}
999
     * @param either     the {@code Either} to convert; must not be {@code null}
1000
     * @param leftMapper a function that maps the left value to a {@link Throwable};
1001
     *                   must not be {@code null} and must not return {@code null}
1002
     * @return {@code Try.success(right)} if the {@code Either} is right, or
1003
     *         {@code Try.failure(leftMapper.apply(left))} if it is left
1004
     * @throws NullPointerException if {@code either} or {@code leftMapper} is {@code null},
1005
     *                              or if {@code leftMapper} returns {@code null}
1006
     */
1007
    static <L, R> Try<R> fromEither(Either<L, R> either, Function<? super L, ? extends Throwable> leftMapper) {
1008
        Objects.requireNonNull(either, "either");
4✔
1009
        Objects.requireNonNull(leftMapper, "leftMapper");
4✔
1010
        if (either.isRight()) {
3✔
1011
            return Try.success(either.getRight());
4✔
1012
        }
1013
        return Try.failure(
4✔
1014
            Objects.requireNonNull(leftMapper.apply(either.getLeft()), "leftMapper returned null")
6✔
1015
        );
1016
    }
1017

1018
    /**
1019
     * Converts this {@code Try} to a standard {@link Optional Optional&lt;V&gt;}.
1020
     *
1021
     * <p>{@code Success(v)} maps to {@code Optional.ofNullable(v)}; {@code Failure} maps to
1022
     * {@link Optional#empty()}. This is the inverse of
1023
     * {@link #fromOptional(Optional, Supplier) fromOptional}, completing the bidirectional bridge
1024
     * between {@code Try} and {@code Optional}.
1025
     *
1026
     * <p><strong>Note:</strong> error details are discarded on failure. Use {@link #toResult()}
1027
     * or {@link #toEither()} to preserve the exception in the converted value.
1028
     *
1029
     * <p>Example:
1030
     * <pre>{@code
1031
     * Try.success("hello").toOptional(); // Optional.of("hello")
1032
     * Try.failure(ex).toOptional();      // Optional.empty()
1033
     * Try.success(null).toOptional();    // Optional.empty()  — null success treated as absent
1034
     * }</pre>
1035
     *
1036
     * @return {@code Optional.of(value)} if this is a non-null {@code Success}, or
1037
     *         {@code Optional.empty()} if this is a {@code Failure} or a {@code Success(null)}
1038
     */
1039
    default Optional<Value> toOptional() {
1040
        return switch (this) {
12✔
1041
            case Success<Value> s -> Optional.ofNullable(s.value());
7✔
1042
            case Failure<Value> _ -> Optional.empty();
1✔
1043
        };
1044
    }
1045

1046
    // ---------- Collectors ----------
1047

1048
    /**
1049
     * A typed container holding the two partitions produced by {@link #partitioningBy()}.
1050
     *
1051
     * @param <V>       the value type of the {@code Success} elements
1052
     * @param successes an unmodifiable list of values from {@code Success} elements, in encounter order
1053
     * @param failures  an unmodifiable list of causes from {@code Failure} elements, in encounter order
1054
     */
1055
    record Partition<V>(List<V> successes, List<Throwable> failures) {
1056
        /**
1057
         * Defensively copies both lists and null-checks them.
1058
         * Uses {@link Collections#unmodifiableList} over a new {@link ArrayList} rather than
1059
         * {@link List#copyOf} so that {@code null} success values produced by
1060
         * {@link Try#run(CheckedRunnable) Try.run(...)} are preserved.
1061
         */
1062
        public Partition {
8✔
1063
            Objects.requireNonNull(successes, "successes");
4✔
1064
            Objects.requireNonNull(failures, "failures");
4✔
1065
            successes = Collections.unmodifiableList(new ArrayList<>(successes));
6✔
1066
            failures  = Collections.unmodifiableList(new ArrayList<>(failures));
6✔
1067
        }
1✔
1068
    }
1069

1070
    /**
1071
     * Returns a {@link Collector} that accumulates a {@code Stream<Try<V>>} into a single
1072
     * {@code Try<List<V>>}, failing on the first {@code Failure} encountered.
1073
     *
1074
     * <p><strong>Note:</strong> this Collector is <em>not</em> fail-fast. All stream elements
1075
     * are always consumed. The accumulator records the first {@code Failure} cause and skips
1076
     * subsequent values; the finisher returns that failure, or {@code Success(List)} if all
1077
     * elements succeeded.
1078
     *
1079
     * <p>Example:
1080
     * <pre>{@code
1081
     * Try<List<String>> result = ids.stream()
1082
     *     .map(id -> Try.of(() -> load(id)))
1083
     *     .collect(Try.toList());
1084
     * }</pre>
1085
     *
1086
     * @param <V> the success value type
1087
     * @return a collector producing {@code Try<List<V>>}
1088
     */
1089
    static <V> Collector<Try<V>, ?, Try<List<V>>> toList() {
1090
        class Acc {
2✔
1091
            final ArrayList<V> values = new ArrayList<>();
5✔
1092
            Throwable firstFailure = null;
4✔
1093
        }
1094
        return Collector.of(
8✔
1095
            Acc::new,
4✔
1096
            (acc, t) -> {
1097
                if (acc.firstFailure != null) return;
4✔
1098
                if (t == null) throw new NullPointerException("toList stream contains a null element");
7✔
1099
                if (t instanceof Failure<V> f) { acc.firstFailure = f.cause(); return; }
11✔
1100
                acc.values.add(((Success<V>) t).value());
7✔
1101
            },
1✔
1102
            (a, b) -> {
1103
                if (a.firstFailure != null) return a;
5✔
1104
                if (b.firstFailure != null) { a.firstFailure = b.firstFailure; return a; }
9✔
1105
                a.values.addAll(b.values);
6✔
1106
                return a;
2✔
1107
            },
1108
            acc -> acc.firstFailure != null
4✔
1109
                ? Try.failure(acc.firstFailure)
4✔
1110
                : Try.success(Collections.unmodifiableList(acc.values))
4✔
1111
        );
1112
    }
1113

1114
    /**
1115
     * Returns a {@link Collector} that partitions a {@code Stream<Try<V>>} into two typed
1116
     * lists: {@link Partition#successes()} for values from {@code Success} elements, and
1117
     * {@link Partition#failures()} for causes from {@code Failure} elements. Both lists are
1118
     * unmodifiable and in encounter order.
1119
     *
1120
     * <p>Example:
1121
     * <pre>{@code
1122
     * Try.Partition<String> p = stream.collect(Try.partitioningBy());
1123
     * p.successes(); // List<String>
1124
     * p.failures();  // List<Throwable>
1125
     * }</pre>
1126
     *
1127
     * @param <V> the success value type
1128
     * @return a collector producing a {@link Partition} of successes and failures
1129
     */
1130
    static <V> Collector<Try<V>, ?, Partition<V>> partitioningBy() {
1131
        class Acc {
2✔
1132
            final ArrayList<V>         successes = new ArrayList<>();
5✔
1133
            final ArrayList<Throwable> failures  = new ArrayList<>();
6✔
1134
        }
1135
        return Collector.of(
8✔
1136
            Acc::new,
4✔
1137
            (acc, t) -> {
1138
                if (t == null) throw new NullPointerException("partitioningBy stream contains a null element");
7✔
1139
                if (t instanceof Success<V> s) acc.successes.add(s.value());
13✔
1140
                else acc.failures.add(((Failure<V>) t).cause());
7✔
1141
            },
1✔
1142
            (a, b) -> { a.successes.addAll(b.successes); a.failures.addAll(b.failures); return a; },
14✔
1143
            acc -> new Partition<>(acc.successes, acc.failures)
8✔
1144
        );
1145
    }
1146

1147
    // ---------- sequence / traverse ----------
1148

1149
    /**
1150
     * Transforms an iterable of {@code Try<V>} into a single {@code Try<List<V>>}.
1151
     * If any element is a {@code Failure}, that failure is returned immediately (fail-fast).
1152
     * If all elements are {@code Success}, returns {@code Success} containing an unmodifiable
1153
     * list of values in encounter order.
1154
     *
1155
     * @param <V>   the value type
1156
     * @param tries the iterable of Try values; must not be {@code null} and must not contain {@code null} elements
1157
     * @return {@code Success(List<V>)} if all elements succeed, or the first {@code Failure} encountered
1158
     * @throws NullPointerException if {@code tries} is {@code null} or contains a {@code null} element
1159
     */
1160
    static <V> Try<List<V>> sequence(Iterable<Try<V>> tries) {
1161
        Objects.requireNonNull(tries, "tries");
4✔
1162
        return sequence(StreamSupport.stream(tries.spliterator(), false));
6✔
1163
    }
1164

1165
    /**
1166
     * Transforms a stream of {@code Try<V>} into a single {@code Try<List<V>>}.
1167
     * If any element is a {@code Failure}, that failure is returned immediately (fail-fast) and
1168
     * the stream is closed. If all elements are {@code Success}, returns {@code Success} containing
1169
     * an unmodifiable list of values in encounter order.
1170
     *
1171
     * @param <V>   the value type
1172
     * @param tries the stream of Try values; must not be {@code null} and must not contain {@code null} elements
1173
     * @return {@code Success(List<V>)} if all elements succeed, or the first {@code Failure} encountered
1174
     * @throws NullPointerException if {@code tries} is {@code null} or contains a {@code null} element
1175
     */
1176
    static <V> Try<List<V>> sequence(Stream<Try<V>> tries) {
1177
        Objects.requireNonNull(tries, "tries");
4✔
1178
        try (var gathered = tries.gather(Gatherer.<Try<V>, ArrayList<V>, Try<List<V>>>ofSequential(
7✔
1179
                ArrayList::new,
1180
                (state, element, downstream) -> {
1181
                    Objects.requireNonNull(element, "tries contains a null element");
4✔
1182
                    if (element instanceof Failure<V> f) {
6✔
1183
                        downstream.push(Try.failure(f.cause()));
6✔
1184
                        return false;
2✔
1185
                    }
1186
                    state.add(((Success<V>) element).value());
6✔
1187
                    return true;
2✔
1188
                },
1189
                (state, downstream) -> downstream.push(Try.success(Collections.unmodifiableList(state)))
7✔
1190
        ))) {
1191
            return gathered.findFirst().orElseThrow();
7✔
1192
        }
1193
    }
1194

1195
    /**
1196
     * Maps each element of an iterable through {@code mapper} and collects the results into a
1197
     * {@code Try<List<B>>}. Fails fast on the first {@code Failure} returned by the mapper.
1198
     *
1199
     * @param <A>    the input element type
1200
     * @param <B>    the mapped value type
1201
     * @param values the iterable of input values; must not be {@code null}
1202
     * @param mapper a function that maps each value to a {@code Try}; must not be {@code null}
1203
     *               and must not return {@code null}
1204
     * @return {@code Success(List<B>)} if all mappings succeed, or the first {@code Failure} produced by the mapper
1205
     * @throws NullPointerException if {@code values} or {@code mapper} is {@code null},
1206
     *                              or if the mapper returns {@code null}
1207
     */
1208
    static <A, B> Try<List<B>> traverse(Iterable<A> values, Function<? super A, Try<B>> mapper) {
1209
        Objects.requireNonNull(values, "values");
4✔
1210
        Objects.requireNonNull(mapper, "mapper");
4✔
1211
        return traverse(StreamSupport.stream(values.spliterator(), false), mapper);
7✔
1212
    }
1213

1214
    /**
1215
     * Maps each element of a stream through {@code mapper} and collects the results into a
1216
     * {@code Try<List<B>>}. Fails fast on the first {@code Failure} returned by the mapper;
1217
     * the stream is closed in all cases.
1218
     *
1219
     * @param <A>    the input element type
1220
     * @param <B>    the mapped value type
1221
     * @param values the stream of input values; must not be {@code null}
1222
     * @param mapper a function that maps each value to a {@code Try}; must not be {@code null}
1223
     *               and must not return {@code null}
1224
     * @return {@code Success(List<B>)} if all mappings succeed, or the first {@code Failure} produced by the mapper
1225
     * @throws NullPointerException if {@code values} or {@code mapper} is {@code null},
1226
     *                              or if the mapper returns {@code null}
1227
     */
1228
    static <A, B> Try<List<B>> traverse(Stream<A> values, Function<? super A, Try<B>> mapper) {
1229
        Objects.requireNonNull(values, "values");
4✔
1230
        Objects.requireNonNull(mapper, "mapper");
4✔
1231
        try (var gathered = values.gather(Gatherer.<A, ArrayList<B>, Try<List<B>>>ofSequential(
8✔
1232
                ArrayList::new,
1233
                (state, element, downstream) -> {
1234
                    Try<B> t = Objects.requireNonNull(mapper.apply(element), "traverse mapper must not return null");
8✔
1235
                    if (t instanceof Failure<B> f) {
6✔
1236
                        downstream.push(Try.failure(f.cause()));
6✔
1237
                        return false;
2✔
1238
                    }
1239
                    state.add(((Success<B>) t).value());
6✔
1240
                    return true;
2✔
1241
                },
1242
                (state, downstream) -> downstream.push(Try.success(Collections.unmodifiableList(state)))
7✔
1243
        ))) {
1244
            return gathered.findFirst().orElseThrow();
7✔
1245
        }
1246
    }
1247

1248
    // ---------- zip / zip3 ----------
1249

1250
    /**
1251
     * Combines two {@code Try} values into a single {@code Try} containing a {@link Tuple2}.
1252
     * Fail-fast: returns the first {@code Failure} encountered.
1253
     *
1254
     * @param <V1> value type of the first try
1255
     * @param <V2> value type of the second try
1256
     * @param t1   first try; must not be {@code null}
1257
     * @param t2   second try; must not be {@code null}
1258
     * @return {@code Success(Tuple2(v1, v2))} if both succeed, otherwise the first {@code Failure}
1259
     * @throws NullPointerException if {@code t1} or {@code t2} is {@code null}
1260
     */
1261
    static <V1, V2> Try<Tuple2<V1, V2>> zip(Try<? extends V1> t1, Try<? extends V2> t2) {
1262
        Objects.requireNonNull(t1, "t1");
4✔
1263
        Objects.requireNonNull(t2, "t2");
4✔
1264
        if (t1 instanceof Failure<?> f) return Try.failure(f.cause());
10✔
1265
        if (t2 instanceof Failure<?> f) return Try.failure(f.cause());
10✔
1266
        V1 v1 = ((Success<? extends V1>) t1).value();
4✔
1267
        V2 v2 = ((Success<? extends V2>) t2).value();
4✔
1268
        return Try.success(new Tuple2<>(v1, v2));
7✔
1269
    }
1270

1271
    /**
1272
     * Combines three {@code Try} values into a single {@code Try} containing a {@link Tuple3}.
1273
     * Fail-fast: returns the first {@code Failure} encountered.
1274
     *
1275
     * @param <V1> value type of the first try
1276
     * @param <V2> value type of the second try
1277
     * @param <V3> value type of the third try
1278
     * @param t1   first try; must not be {@code null}
1279
     * @param t2   second try; must not be {@code null}
1280
     * @param t3   third try; must not be {@code null}
1281
     * @return {@code Success(Tuple3(v1, v2, v3))} if all three succeed, otherwise the first {@code Failure}
1282
     * @throws NullPointerException if any argument is {@code null}
1283
     */
1284
    static <V1, V2, V3> Try<Tuple3<V1, V2, V3>> zip3(
1285
            Try<? extends V1> t1,
1286
            Try<? extends V2> t2,
1287
            Try<? extends V3> t3) {
1288
        Objects.requireNonNull(t1, "t1");
4✔
1289
        Objects.requireNonNull(t2, "t2");
4✔
1290
        Objects.requireNonNull(t3, "t3");
4✔
1291
        if (t1 instanceof Failure<?> f) return Try.failure(f.cause());
10✔
1292
        if (t2 instanceof Failure<?> f) return Try.failure(f.cause());
10✔
1293
        if (t3 instanceof Failure<?> f) return Try.failure(f.cause());
10✔
1294
        V1 v1 = ((Success<? extends V1>) t1).value();
4✔
1295
        V2 v2 = ((Success<? extends V2>) t2).value();
4✔
1296
        V3 v3 = ((Success<? extends V3>) t3).value();
4✔
1297
        return Try.success(new Tuple3<>(v1, v2, v3));
8✔
1298
    }
1299

1300
    /**
1301
     * Combines three {@code Try} values using a {@link TriFunction}.
1302
     * Fail-fast: returns the first {@code Failure} encountered.
1303
     *
1304
     * @param <V1>     value type of the first try
1305
     * @param <V2>     value type of the second try
1306
     * @param <V3>     value type of the third try
1307
     * @param <R>      result value type
1308
     * @param t1       first try; must not be {@code null}
1309
     * @param t2       second try; must not be {@code null}
1310
     * @param t3       third try; must not be {@code null}
1311
     * @param combiner function applied to the three values; must not be {@code null}
1312
     * @return {@code Success(combiner(v1, v2, v3))} if all succeed, otherwise the first {@code Failure}
1313
     * @throws NullPointerException if any argument is {@code null}
1314
     */
1315
    static <V1, V2, V3, R> Try<R> zipWith3(
1316
            Try<? extends V1> t1,
1317
            Try<? extends V2> t2,
1318
            Try<? extends V3> t3,
1319
            TriFunction<? super V1, ? super V2, ? super V3, ? extends R> combiner) {
1320
        Objects.requireNonNull(t1, "t1");
4✔
1321
        Objects.requireNonNull(t2, "t2");
4✔
1322
        Objects.requireNonNull(t3, "t3");
4✔
1323
        Objects.requireNonNull(combiner, "combiner");
4✔
1324
        if (t1 instanceof Failure<?> f) return Try.failure(f.cause());
10✔
1325
        if (t2 instanceof Failure<?> f) return Try.failure(f.cause());
10✔
1326
        if (t3 instanceof Failure<?> f) return Try.failure(f.cause());
10✔
1327
        V1 v1 = ((Success<? extends V1>) t1).value();
4✔
1328
        V2 v2 = ((Success<? extends V2>) t2).value();
4✔
1329
        V3 v3 = ((Success<? extends V3>) t3).value();
4✔
1330
        R result;
1331
        try {
1332
            result = combiner.apply(v1, v2, v3);
6✔
1333
        } catch (Throwable t) {
1✔
1334
            return Try.failure(t);
3✔
1335
        }
1✔
1336
        return Try.success(result);
3✔
1337
    }
1338

1339
    // ---------- zip4 ----------
1340

1341
    /**
1342
     * Combines four {@code Try} values into a single {@code Try} containing a {@link Tuple4}.
1343
     * Fail-fast: returns the first {@code Failure} encountered.
1344
     *
1345
     * @param <V1> value type of the first try
1346
     * @param <V2> value type of the second try
1347
     * @param <V3> value type of the third try
1348
     * @param <V4> value type of the fourth try
1349
     * @param t1   first try; must not be {@code null}
1350
     * @param t2   second try; must not be {@code null}
1351
     * @param t3   third try; must not be {@code null}
1352
     * @param t4   fourth try; must not be {@code null}
1353
     * @return {@code Success(Tuple4(v1,v2,v3,v4))} if all four succeed, otherwise the first {@code Failure}
1354
     * @throws NullPointerException if any argument is {@code null}
1355
     */
1356
    static <V1, V2, V3, V4> Try<Tuple4<V1, V2, V3, V4>> zip4(
1357
            Try<? extends V1> t1,
1358
            Try<? extends V2> t2,
1359
            Try<? extends V3> t3,
1360
            Try<? extends V4> t4) {
1361
        Objects.requireNonNull(t1, "t1");
4✔
1362
        Objects.requireNonNull(t2, "t2");
4✔
1363
        Objects.requireNonNull(t3, "t3");
4✔
1364
        Objects.requireNonNull(t4, "t4");
4✔
1365
        if (t1 instanceof Failure<?> f) {
6✔
1366
            return Try.failure(f.cause());
4✔
1367
        }
1368
        if (t2 instanceof Failure<?> f) {
6✔
1369
            return Try.failure(f.cause());
4✔
1370
        }
1371
        if (t3 instanceof Failure<?> f) {
6✔
1372
            return Try.failure(f.cause());
4✔
1373
        }
1374
        if (t4 instanceof Failure<?> f) {
6✔
1375
            return Try.failure(f.cause());
4✔
1376
        }
1377
        V1 v1 = ((Success<? extends V1>) t1).value();
4✔
1378
        V2 v2 = ((Success<? extends V2>) t2).value();
4✔
1379
        V3 v3 = ((Success<? extends V3>) t3).value();
4✔
1380
        V4 v4 = ((Success<? extends V4>) t4).value();
4✔
1381
        return Try.success(new Tuple4<>(v1, v2, v3, v4));
9✔
1382
    }
1383

1384
    /**
1385
     * Combines four {@code Try} values using a {@link QuadFunction}.
1386
     * Fail-fast: returns the first {@code Failure} encountered.
1387
     * If the combiner throws, the exception is captured as a {@code Failure}.
1388
     *
1389
     * @param <V1>     value type of the first try
1390
     * @param <V2>     value type of the second try
1391
     * @param <V3>     value type of the third try
1392
     * @param <V4>     value type of the fourth try
1393
     * @param <R>      result value type
1394
     * @param t1       first try; must not be {@code null}
1395
     * @param t2       second try; must not be {@code null}
1396
     * @param t3       third try; must not be {@code null}
1397
     * @param t4       fourth try; must not be {@code null}
1398
     * @param combiner function applied to the four values; must not be {@code null}
1399
     * @return {@code Success(combiner(v1,v2,v3,v4))} if all succeed, otherwise the first {@code Failure}
1400
     * @throws NullPointerException if any argument is {@code null}
1401
     */
1402
    static <V1, V2, V3, V4, R> Try<R> zipWith4(
1403
            Try<? extends V1> t1,
1404
            Try<? extends V2> t2,
1405
            Try<? extends V3> t3,
1406
            Try<? extends V4> t4,
1407
            QuadFunction<? super V1, ? super V2, ? super V3, ? super V4, ? extends R> combiner) {
1408
        Objects.requireNonNull(t1, "t1");
4✔
1409
        Objects.requireNonNull(t2, "t2");
4✔
1410
        Objects.requireNonNull(t3, "t3");
4✔
1411
        Objects.requireNonNull(t4, "t4");
4✔
1412
        Objects.requireNonNull(combiner, "combiner");
4✔
1413
        if (t1 instanceof Failure<?> f) {
6✔
1414
            return Try.failure(f.cause());
4✔
1415
        }
1416
        if (t2 instanceof Failure<?> f) {
6✔
1417
            return Try.failure(f.cause());
4✔
1418
        }
1419
        if (t3 instanceof Failure<?> f) {
6✔
1420
            return Try.failure(f.cause());
4✔
1421
        }
1422
        if (t4 instanceof Failure<?> f) {
6✔
1423
            return Try.failure(f.cause());
4✔
1424
        }
1425
        V1 v1 = ((Success<? extends V1>) t1).value();
4✔
1426
        V2 v2 = ((Success<? extends V2>) t2).value();
4✔
1427
        V3 v3 = ((Success<? extends V3>) t3).value();
4✔
1428
        V4 v4 = ((Success<? extends V4>) t4).value();
4✔
1429
        R result;
1430
        try {
1431
            result = combiner.apply(v1, v2, v3, v4);
7✔
1432
        } catch (Throwable t) {
1✔
1433
            return Try.failure(t);
3✔
1434
        }
1✔
1435
        return Try.success(result);
3✔
1436
    }
1437

1438
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc