Try¶
Try<A> is the outcome of a computation that may throw: Success(A value) or Failure(Throwable cause).
When to use it¶
Use Try around code that throws: parsing, I/O, a library that reports errors with exceptions.
- If the error is a value you define rather than an exception, use
Either.toEither()converts. - If several independent checks must all be reported, use
Validation.
Construction¶
Try.of takes a Callable, so checked exceptions need no wrapping. Try.run is for a computation with no result;
its success value is the empty tuple ().
var parsed = Try.of(() -> Integer.parseInt("42")); // Try<Integer>
var failed = Try.of(() -> Integer.parseInt("forty-two")); // Try<Integer>
var ran = Try.run(() -> Thread.sleep(1)); // Try<Tuple0>
// Success(42), Failure(java.lang.NumberFormatException: For input string: "forty-two"), Success(())
Try.success and Try.failure build a case directly.
Resources¶
To open resources, use them and close them with the outcome in a Try, see Using.
What is captured¶
Try captures every exception or error except the fatal ones, which are rethrown: InterruptedException,
LinkageError, ThreadDeath and VirtualMachineError (so OutOfMemoryError and StackOverflowError).
Pattern matching over the cases¶
Success and Failure are records, so pattern matching with a switch expression needs no default:
var result = Try.of(() -> Integer.parseInt("x")); // Try<Integer>
var report = switch (result) {
case Success(var value) -> "parsed " + value;
case Failure(var cause) -> "failed: " + cause.getClass().getSimpleName();
};
// "failed: NumberFormatException"
fold(ifFailure, ifSuccess) does the same with two functions.
Recovering from a failure¶
catchSome recovers from one type of exception and leaves the others alone. catchAll recovers from any.
var port = Try.of(() -> Integer.parseInt("80a"))
.catchSome(NumberFormatException.class, e -> 8080)
.map(p -> p + 1); // Try<Integer>
// Success(8081)
mapError replaces the cause, typically to wrap it in an exception of your domain.
var recovered = Try.of(() -> Integer.parseInt("x")).catchAll(e -> 0); // Try<Integer>
var wrapped = Try.<Integer>failure(new java.io.IOException("disk"))
.mapError(e -> new IllegalStateException("cannot read the configuration", e)); // Try<Integer>
// Success(0), Failure(java.lang.IllegalStateException: cannot read the configuration)
catchAllWith and catchSomeWith recover with another Try, which may fail in turn. getOrElse(Function) builds
a value from the cause.
Chaining steps that throw¶
map and flatMap run under Try: an exception thrown by the function becomes the Failure. filter fails a
value that does not pass a test, with the exception you build.
var ratio = Try.of(() -> 10).map(n -> 100 / (n - 10)); // Try<Integer>
var positive = Try.success(-1)
.filter(n -> n > 0, n -> new IllegalArgumentException("not positive: " + n)); // Try<Integer>
// Failure(java.lang.ArithmeticException: / by zero), Failure(java.lang.IllegalArgumentException: not positive: -1)
ensuring runs an action whatever the outcome, like a finally block.
var log = new StringBuilder();
var done = Try.of(() -> 1).ensuring(() -> log.append("closed")); // Try<Integer>
// Success(1), and log is "closed"
Other members:
mapTryandflatMapTrytake a function that throws checked exceptions.andThenandandThenTryrun a side effect on a success; if it throws, the result is aFailure.tapruns an action on the value;tapErroron the cause, optionally only for one exception type.zipandzipWithcombine severalTrys and stop at the firstFailure; see zip at arity N.
Conversions¶
toEither()gives anEither<Throwable, A>;toValidation()aValidation<Throwable, A>.toOption()andtoVector()keep the value and drop the cause.toCompletableFuture()gives an already completed future.Try.fromCompletableFuturewaits for a future and captures its outcome.
var either = Try.of(() -> Integer.parseInt("7")).toEither(); // Either<Throwable, Integer>
var future = Try.success(7).toCompletableFuture(); // CompletableFuture<Integer>
var back = Try.fromCompletableFuture(future); // Try<Integer>
// Right(7), a completed future, Success(7)
Sharp edges¶
Two failures are rarely equal¶
Failure compares its cause with the cause's own equals. JDK exceptions do not override it, so two failures are
equal only if they hold the same exception object.
Two failures of the same input are not equal. Compare the class or the message of the cause instead.
var first = Try.of(() -> Integer.parseInt("x")); // Try<Integer>
var second = Try.of(() -> Integer.parseInt("x")); // Try<Integer>
var same = first.equals(second);
var sameClass = first.getCause().getClass() == second.getCause().getClass();
// same is false, sameClass is true
get() rethrows the cause¶
get() on a Failure throws the cause itself, even a checked exception that the method does not declare. Prefer
pattern matching, fold or getOrElse.
null is a failure¶
A Success never holds null. A computation that returns null gives a Failure of a NullPointerException.
This includes Try.fromCompletableFuture on a future completed with null, such as a CompletableFuture<Void>: map
it to a value first.
Fatal errors escape¶
The fatal errors listed above are rethrown by every method, never captured. Try is not a way to survive an
OutOfMemoryError.