benjiPosted under Java.

There are different viewpoints on how to use exceptions effectively in Java. Some people like checked exceptions, some argue they are a failed experiment and prefer exclusive use of unchecked exceptions. Others eschew exceptions entirely in favour of passing and returning types like Optional or Maybe.

Whatever your view, you are likely to need to interact with libraries written by people with a different usage of Exceptions. This often leads to ugly code to translate failure conditions into your preferred approach.

This is likely to become more of a pain with Java 8, where the Streams api doesn’t play very nicely with methods that throw exceptions.

Fortunately, Java 8 also helps us translate between styles of exception usage more easily.

Using Streams with Exceptions

Using the streams api can become painful when you wish to use methods that throw exceptions as maps or filters.

Take this example. What happens if Example.duplicatesShortStrings throws a checked exception? It will fail to compile.

The map method on the stream interface expects a Function that takes an argument and returns a value without any exceptions in its method signature.

If map did accept functions that can throw exceptions then the whole chain of transformations would fail if that exception were thrown on any given element. In this example the string “booooo” is too long and will cause an exception, which means we wouldn’t get any results at all.

asList("foo", "bar", "baz", "booooo")
    .stream()
    .map(Example::duplicatesShortStrings) // This will fail to compile 
    .collect(Collectors.toList());
 
...
 
String duplicatesShortStrings(String input) throws InputTooLongException {
    if (input.length() > 3) throw new InputTooLongException();

How can we handle this kind of scenario better? In functional languages you might return either the result or an error from a function rather than having exceptions. The method from the example above might look more like.

Result<String, InputTooLong> duplicatesShortStrings(String input)

That’s all very well, and easier to work with, but we are likely to be consuming lots of existing code using exceptions which we are not able to re-write.

You’ll also notice that there both of these are basically equivalent

Result<String, InputTooLong> duplicatesShortStrings(String input)
String duplicatesShortStrings(String input) throws InputTooLong

What if we provide a way to translate both ways between these two styles. Then we could use streams without problems. It’s mostly possible to do this. Unfortunately, we can’t record all the possible failure conditions with generics due to lack of varargs for generic type parameters. We can, however, achieve the following.

List<String> result =
    asList("foo", "bar", "baz", "boooo")
        .stream()
        .map(Result.wrapReturn(Example::duplicatesShortStrings))
        .map(Result.wrap(s -> s.toUpperCase()))
        .filter(Result::success)
        .map(Result::unwrap)
        .collect(Collectors.toList());
 
assertEquals(asList("FOOFOO","BARBAR","BAZBAZ"), result);

Here we convert our method that throws an exception into a function that returns a Result type that encodes whether it is a success or failure and the type of failure. We can then continue to chain operations using the streams api, but as the stream is now a Stream<Result<String>> rather than a Stream<String> we need to wrap any functions that normally operate on Strings to operate on a Result<String> instead.

After we’ve finished we can filter out items that did not result in an error. We also have the option of handling the errors if we wished, e.g. to provide a default value.

Here we substitute the default “OhNoes” value for any entries in the stream for which a stream operation has resulted in an InputTooLongException.

String result = asList("foo", "bar", "baz", "boooo")
    .stream()
    .map(Result.wrapReturn(Example::duplicatesShortStrings))
    .map(
        Result.onSuccess((String s) -> s.toUpperCase())
              .on(InputTooLongException.class, s -> "OhNoes")
              .mapper()
    )
    .filter(Result::success)
    .map(Result::unwrap)
    .collect(Collectors.toList());
 
assertEquals(asList("FOOFOO","BARBAR","BAZBAZ", "OhNoes"), result);

To implement this we need a Result type that wraps our actual result, with two implementations – Success and Failure. Success taking a value and Failure taking an Exception

public interface Result<T> { ... }
class Success<T> implements Result<T> { ... }
class Failure<T> implements Result<T> { ... }

We then just need to provide a helper function that transforms a method that throws an Exception into a function that returns a Result.

public static <T, R, E extends Exception> 
    Function<T,Result<R>> wrapReturn(ExceptionalFunction<T,R,E> f) {
        return t -> {
            try {
                return new Success<R>(f.apply(t));
            } catch (Exception e) {
                return new Failure<R>(e);
            }
        };
    }

and a function that transforms a function that operates on unwrapped types and changes it to operate on wrapped types (for the middle of the stream)

public static <T, R> Function<Result<T>,Result<R>> wrap(Function<T,R> f) {
    return t -> {
        try {
            return t.map(f);
        } catch (Exception e) {
            return new Failure<R>(e);
        }
    };
}

Exception Handling Patterns

There are also a couple of very common exception handling patterns which we can make more concise.

Everything unchecked

This approach involves wrapping all checked exceptions in runtime exceptions. This can be useful in the case where we don’t have a good way to handle the checked exceptions thrown by code we are calling, nor is it important for anything higher up the callstack to handle them. It is also useful if you don’t wish to use checked exceptions at all.

Normally this would look something like

try {
    String foo = Example.methodThatThrowsCheckedException(THROW);
} catch (ACheckedExceptionIDontHaveAGoodWayToDealWith e) {
    throw new RuntimeException(e);
}

Now we can pull that out to a method that accepts a function and have something likely

@Test(expected = RuntimeException.class)
public void checked_to_unchecked_should_wrap_in_runtime_exception() {
    String foo = unchecked(() -> Example.methodThatThrowsCheckedException(THROW));
}

where unchecked looks something like the following (similar implementation for void methods)

public interface ExceptionalSupplier<T> {
    T supply() throws Exception;
}
 
public static <T> T unchecked(ExceptionalSupplier<T> supplier) {
    try {
        return supplier.supply();
    } catch (Error | RuntimeException rex) {
        throw rex;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Wrapping in domain specific exception

Another common pattern is catching all exceptions that may occur at a lower layer in the application and wrapping them in a more meaningful exception to consumers of your code.

This used to look like this

try {
    return Example.methodThatThrowsCheckedException(THROW);
} catch (ACheckedExceptionIDontHaveAGoodWayToDealWith e) {
    throw new Wrapped(e);
}

Again we can do the same and provide something like this, where we provide a reference to the constructor of the exception that we want to wrap exceptions in. Our wrapChecked method can now construct and throw the Wrapped exception.

@Test(expected = Wrapped.class)
public void wrapping_checked() throws Wrapped {
    wrappingChecked(() -> Example.methodThatThrowsCheckedException(THROW)).in(Wrapped::new);
}

Implementation and examples are on github as usual.

Leave a Reply

  • (will not be published)