A Hitchhiker's Guide to the Functional Exception Handling in Java

Grzegorz Piwowarek @pivovarit


Software Engineer | Trainer

commiter @ AssertJ, Vavr

blogger @ 4Comprehension

senior editor @ Baeldung


  • Invisible in the source code
  • Create unexpected exit points
  • Behave like GOTOs
  • Easy to lose when async

"Every time you call a function that can raise an exception and don’t catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn’t think about."

October 13, 2003 by Joel Spolsky

Exceptions in Java are abused

  • Modeling absence
  • Modeling known business cases
  • Modeling alternate paths
     * Fills in the execution stack trace. This method records within this
     * {@code Throwable} object information about the current state of
     * the stack frames for the current thread.
     * ...
    public synchronized Throwable fillInStackTrace() {

...and are not cheap.




Java Exceptions Antipatterns

  • Log and throw
  • Declaring that your method throws java.lang.Exception
  • Declaring that your method throws a large variety of exceptions
  • Catching java.lang.Exception
  • Destructive wrapping (throwing away stacks)
  • Log and return null
  • Catch and ignore
  • Throw within finally
  • Multi-line log messages
  • Unsupported operation returning null
  • Ignoring InterruptedException
  • Relying on getCause()


Error Handling in Go!

func foo(s string) (string, error) {
    // implementation

"Values can be programmed, and since errors are values, errors can be programmed. Errors are not like exceptions. There’s nothing special about them, whereas an unhandled exception can crash your program."


response, err := foo("Dumbledore dies.")
if err != nil {
    // ...
// happy path like nothing happened

Error handling in Go does not disrupt the flow of control.

panic keyword


Compile-time checks result in shorter feedback cycles

public class Person {
    private final String name;
    private final String surname;
    private final String address;
    private final String phoneNumber;
    private final String age;


public class Person {
    private final String name;
    private final Surname surname;
    private final Address address
    private final PhoneNumber phoneNumber;
    private final PositiveInteger age;


Optionality encapsulated

Person findOne(long id);

Optional<Person> findOne(long id);
sealed abstract class Try[+T]

final case class Success[+T](value: T) extends Try[T] { ... } 
final case class Failure[+T](exception: Throwable) extends Try[T] { ... }

Try in Scala

sealed -> all possible implementations were provided

public interface Try<T> { ... } 

final class Failure<T> implements Try<T> { ... }
final class Success<T> implements Try<T> { ... }

Try in Java(Vavr)

No sealed in Java  (╯°□°)╯︵ ┻━┻)

static <T> Try<T> of(CheckedFunction0<? extends T> supplier) {
        Objects.requireNonNull(supplier, "supplier is null");
        try {
            return new Success<>(supplier.apply());
        } catch (Throwable t) {
            return new Failure<>(t);

Try in Java(Vavr) - instantiation

Try<U> map(Function<? super T, ? extends U> mapper)
Try<U> flatMap(Function<? super T, ? extends Try<? extends U>> mapper)
Try<T> filter(Predicate<? super T> predicate) ...

Try<T> orElse(Try<? extends T> other)

Try<T> onFailure(Consumer<? super Throwable> action)
Try<T> onSuccess(Consumer<? super T> action)

Try<T> recover(Class<X> exception, T value)

T get(); // do not use that.


List<URL> getSearchResults(String searchString) throws IOException

Try in Action I - consumer side

Try<List<URL>> getSearchResults(String searchString) { ... }
 .onFailure(ex -> LOG.info("Houston, we have a problem:" + ex.getMessage()))
 .getOrElse(() -> Collections.singletonList(javaday));
List<URL> getFromGoogle(String search) throws NoSuchElementException, IOException

Try in Action II - producer side

List<URL> getFromDuckDuckGo(String search) throws IOException
static Try<List<URL>> getSearchResults(String searchString) {
    return Try.of(() -> getFromGoogle(searchString))
      .recover(NoSuchElementException.class, emptyList())
      .recover(NSAForbiddenException.class, emptyList())
      .orElse(() -> Try.of(() -> getFromDuckDuckGo(searchString)));
static Try<URL> getSingleSearchResults(String searchString)

Try in Action III - flatmap

  .map(url -> url.openStream()) //Unhandled exception
 Try<Try<InputStream>> javaday = getSingleSearchResults("javaday")
  .map(url -> Try.of(url::openStream));

 Try<InputStream> javaday = getSingleSearchResults("javaday")
  .flatMap(url -> Try.of(url::openStream))

Try in Action IV - pattern matching

Try<InputStream> javaday = getSingleSearchResults("javaday")
  .flatMap(url -> Try.of(url::openStream))

  Case($Success($()), "Opened successfully"),
  Case($Failure($()), "Failed :(")
Tuple2<Person, ErrorObject> findOne(long id);

Tuple2<Person, ErrorObject> res = findOne(42);
if (res._2 != null) { ... }
if (res._1 != null) { ... }

Omnipresent "Java" feel instead

Simulating Go's feel with Tuple

Tuples are expected to hold non-nullable values

sealed abstract class Either[+A, +B]

final case class Left[+A, +B](value: A) extends Either[A, B]
final case class Right[+A, +B](value: B) extends Either[A, B]

Either in Scala

  • Disjoint union
  • Try<T> is isomorphic to Either<Throwable, T>​
  • By convention, Right is success and Left is failure
  • In Scala, Either is right-biased now*
interface Either<L, R> 

final class Left<L, R> implements Either<L, R>
final class Right<L, R> implements Either<L, R>

Either in Java(Vavr)


Either instantiation

Separate factory methods for Left and Right:

LeftProjection<L, R> left()
RightProjection<L, R> right()

Either<X, Y> bimap(Function<..., X> leftMapper, Function<..., Y> rightMapper)

U fold(Function<..., U> leftMapper, Function<..., U> rightMapper)

R getOrElseGet(Function<? super L, ? extends R> other)

Either<R, L> swap()

L getLeft()  // do not use that unless you know what you are doing
L getRight() // yup, you guessed it.

Either API

case class FetchError(msg: String, response: HttpResponse)

Either in action - error object

public static class FetchError {
        private final String msg;
        private final HttpResponse response;

        public FetchError(String msg, HttpResponse response) {
            this.msg = msg;
            this.response = response;

        public String getMsg() {
            return msg;

        public HttpResponse getResponse() {
            return response;

        public String toString() {
            return "FetchError{" +
              "msg='" + msg + '\'' +
              ", response=" + response +

        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            FetchError that = (FetchError) o;

            if (msg != null ? !msg.equals(that.msg) : that.msg != null) return false;
            return response != null ? response.equals(that.response) : that.response == null;

        public int hashCode() {
            int result = msg != null ? msg.hashCode() : 0;
            result = 31 * result + (response != null ? response.hashCode() : 0);
            return result;

Either in action - error object Java

Try<List<URL>> getSearchResults(String searchString)

Either<FetchError, List<URL>> getSearchResults(String searchString)


Either in action

public static Either<DnsUrl, URL> resolve(String url)

Modeling alternative paths

/** Returns either the next step of the tailcalling computation,
 * or the result if there are no more steps. */
@annotation.tailrec final def resume: Either[() => TailRec[A], A] = this match {
    case Done(a) => Right(a)
    case Call(k) => Left(k)
    case Cont(a, f) => a match {
      case Done(v) => f(v).resume
      case Call(k) => Left(() => k().flatMap(f))
      case Cont(b, g) => b.flatMap(x => g(x) flatMap f).resume

Modeling alternative paths - Scala TailCalls


Absence modeling

Do we really need exceptions here? 

Person findOne(long id) throws NoSuchElementException;
Integer valueOf(String s) throws NumberFormatException;
Optional<Person> findOne(long id);
Optional<Integer> valueOf(String s);

Why not?

Absence modeling

What if your language does not support that?

  • Switch language.
  • Build it yourself.
  • Use vavr.io (formerly known as Javaslang)
  • Take part in Java Community Process

Key Takeaways

  • Exceptions work best when you do not expect people to recover from them
  • Try can be used for representing computations that may throw an exception
  • Absence can be modelled with Option
  • Either can be used for advanced scenarios involving error objects and modeling alternative paths
  • VAVR is the new Guava

"To Try or not to Try, there is no throws"

Thank You!

Yoda, ~41:3

Grzegorz Piwowarek @pivovarit


Functional Error Handling Raúl Raja Martínez​

The Neophyte's Guide to Scala Daniel Westheide



A Hitchhiker's Guide to the Functional Exception Handling in Java

By Grzegorz Piwowarek

A Hitchhiker's Guide to the Functional Exception Handling in Java

  • 10,016