Elegantes Exception Handling mit der Try-Monade

Elegant Exception Handling Using the Try Monad


Thorough exception handling is essential to achieve robust software. Typically, exceptions are either handled locally by try{}catch(SpecificException ex){} or in a centralized manner. The latter means that exceptions are propagated up the call hierarchy and actual handling is implemented by multiple catch-blocks at a single place. Local handling has a negative impact on code readability. In particular, checked exceptions are quite unpopular among programmers since they enforce either local handling or explicit upwards propagation by the throws-clause.

This situation even lead to Java developments such as Lombok's @SneakyThrows where checked exceptions are converted into runtime exceptions by automated code generation. On the other hand, centralized handling is conceptually similar to GOTO because the actual program flow is interrupted. In professional practice, we mostly find a combination of both approaches. In scary cases, centralized handling is even used to start new program flows.


In recent years, several concepts that originate from functional programming made their way to the mainstream of software development. In the Java language, examples are lambda expressions as well as the monads Optional and Stream. With regard to exception handling, there is a functional concept: Railway Oriented Programming. It is based on monads such as Try or Result.

The Java library VAVR includes an implementation of the Try monad. Its usage is not more complex than the (intended) usage of Optional. By calling Try.of(() -> ...) a supplier function is wrapped in the monadic context Try<T> which is either Success<T> or Failure. After execution, either the function's result (Type T) or a Throwable will be stored for further processing. For example, operators such as try.map(x -> ...), whose input is the inner value, can be specified but will only be executed in the context of Success. However, if an exception has occurred, the operator will do nothing but just passing on the Throwable. Similar to this, it is also possible to specify operations on the Throwable with mapFailure or to change context with flatMap.


Try<Integer> myTry = Try
 .of(() -> 10 / 2)
 .map(i -> i + 1);
 i -> assertEquals(6, i));

Try<Integer> myTry = Try
 .of(() -> 10 / 0)
 .map(i -> i + 1);

  ex -> assertTrue(
    ex instanceof
From Failure to Success
// Generally
Try<Integer> try2 = myTry
  .orElse(() -> 0)
// For specific exceptions
Try<Integer> try2 = myTry.recover(
  ex -> 0)

From Success to Failure
Try.of(() -> send(request))
   .flatMap(resp -> resp.isEmpty()
     ? Try.failure(new MyExptn())
     : Try.success(resp));

Access to inner value
T t = myTry.getOrElseGet(ex -> ...)
Either<Throwable, T> =
Exception Handling in Streams
List<Object> input = List.of(...);
List<Try<Object>> trys =
       .map(o ->
         Try.of(() -> o)
      t -> t.onFailure(log::warn));
List<Object> output =
      .map(t -> t.getOrElseGet(
        ex -> ...))

Further Aspects

  • The VAVR user guide contains a shot introduction but Baeldung provides a better guide.
  • It's best to have a look at the well documented Quellcode!
  • The general concept of Railway Oriented Programming will be explained in this video.
  • A further implementation of Try can be found in the Scala language.


Author: André Petermann / Software Architect / jambit Office Leipzig

Elegant Exception Handling Using the Try Monad

Cookie Settings

This website uses cookies to personalize content and ads, provide social media features, and analyze website traffic. In addition, information about your use of the website is shared with social media, advertising, and analytics partners. These partners may merge the information with other data that you have provided to them or that they have collected from you using the services.

For more information, please refer to our privacy policy. There you can also change your cookie settings later on.

contact icon

Contact us now