Functional style error handling in Java

In addition to the classical approach for handling errors using exceptions, a functional approach can also be distinguished.



Instead of throwing an exception right away, you can localize it and then perform certain actions on it.



For example, in Scala, a specific Try class is used for this.



def inputStreamForURL(url: String): Try[Try[Try[InputStream]]] = parseURL(url).map { u => Try(u.openConnection()).map(conn => Try(conn.getInputStream)) }
      
      





In the Java world, using the Vavr library, you can also handle errors in a functional style.



 Try.of(() -> u.openConnection()).getOrElse(other);
      
      





In Java 8, the Optional class was added to work more correctly with null types. It allows you to wrap an object, which can be null, and in a functional style it is safe to work with it further.



To work with different types of exceptions, you can implement a class similar to Optional, which is also thread-safe and non-mutable.



 public final class Expected<T, E extends Throwable> { private final T value; private final E error; private Expected() { this.value = null; this.error = null; } private Expected(T value) { this.value = Objects.requireNonNull(value); this.error = null; } private Expected(E error) { this.error = Objects.requireNonNull(error); this.value = null; } public static <T, E extends Throwable> Expected<T, E> of(T value) { return new Expected<>(value); } public static <T, E extends Throwable> Expected<T, E> of(E error) { return new Expected<>(error); } public static <T, E extends Throwable> Expected<T, E> of(Supplier<T> supplier) { try { return new Expected<>(supplier.get()); } catch (Throwable e) { return new Expected<>((E) e); } } public boolean isValue() { return value != null; } public boolean isError() { return error != null; } public T value() { return value; } public E error() { return error; } }
      
      





Also, to check whether an exception was thrown or not, you can write a simple visitor.



 Expected<Integer, SQLException> data = Expected.of(new SQLException()); matches(data, Expected::error, e -> out.println("get error: " + e), Expected::value, v -> out.println("get value: " + v) ); Expected<Integer, ArithmeticException> expression = Expected.of(() -> 4 / 0); matches(expression, Expected::error, e -> out.println("get error: " + e), Expected::value, v -> out.println("get value: " + v) );
      
      





 public static <T, E extends Throwable> void matches(Expected<T, E> value, Function<Expected<T, E>, E> firstFunction, Consumer<E> firstBranch, Function<Expected<T, E>, T> secondFunction, Consumer<T> secondBranch) { if (value.isError()) { E arg = firstFunction.apply(value); firstBranch.accept(arg); } else { T arg = secondFunction.apply(value); secondBranch.accept(arg); } }
      
      





In C ++ 23, it is planned to add a similar class to the standard library.



Such a class could constitute a good Optional company in the standard Java library. But alas, at the moment, to work with exceptions in a functional style, you can use the Vavr library or write your own classes in the likeness of Expected.



The full source code of the class can be viewed on github: link .



All Articles