created: March 01, 2016

updated: March 03, 2016



One problem with using lambda's in Java 8, is that dealing with checked exceptions results in unnecessary exception handling code. Consider a lambda of Function where you need to deal with an IOException :

public List < String > mapFiles ( List < File > files , Function < File , String > function ) { return files . stream (). map ( function ). collect ( Collectors . toList ()); } public List < String > readFiles ( List < File > files ) { return mapFiles ( files , FunctionWithIO . toSafeFunction ( file -> { try { // some method that throws IOException return IOUtils . toString ( file ); } catch ( IOException io ) { throw new RuntimeException ( io ); } })); }

To add try-catch statements in all of our lambda's is a tedious exercise, and only helps us hide exceptions. We can start to improve the situation with the following interface and helper method:

@FunctionalInterface public interface FunctionWithIO < A , B > { // The functional interface method, notice it declares a throws exception. B apply ( A value ) throws IOException ; // helper method to catch the declared method and rethrow as a checked exception. public static < A , B > Function < A , B > toSafeFunction ( final FunctionWithIO < A , B > functionWithIO ) { return ( value ) -> { try { return functionWithIO . apply ( value ); } catch ( IOException io ) { throw new RuntimeException ( io ); } }; } }

And now our original method is simplified to:

public List < String > mapFiles ( List < File > files , Function < File , String > function ) { return files . stream (). map ( function ). collect ( Collectors . toList ()); } public List < String > readFiles ( List < File > files ) { return mapFiles ( files , FunctionWithIO . toSafeFunction ( file -> { // some method that throws IOException return IOUtils . toString ( file ); })); }

We can take this further...

Currently, if needed checked exceptions thrown inside a forEach or map method on Stream, we have to use toSafeFunction to turn this interface into a usable Function interface. If the interface FunctionWithIO extended the original Function interface, it could be used directly.

@FunctionalInterface public interface FunctionWithIO < A , B > extends Function < A , B > { // The functional interface method, notice it declares a throws exception. B applyWithIO ( A value ) throws IOException (); @Overwrite default B apply ( A value ) { try { return functionWithIO . apply ( value ); } catch ( IOException io ) { throw new RuntimeException ( io ); } } // helper method to cast lambda's as FunctionWithIO. public static < A , B > FunctionWithIO < A , B > castFunctionWithIO ( final FunctionWithIO < A , B > functionWithIO ) { return functionWithIO ; } }

Now we only have to specify that its the 'WithIO' interface in mapFile's, and everything is taken care of.

public List < String > mapFiles ( List < File > files , FunctionWithIO < File , String > functionWithIO ) { return files . stream (). map ( functionWithIO ). collect ( Collectors . toList ()); } public List < String > readFiles ( List < File > files ) { return mapFiles ( files , file -> { // some method that throws IOException return IOUtils . toString ( file ); }); }

The Library

To solve this problem for all interfaces and for exceptions other than just IOException, I've created throwable-interfaces ( on github ). For any Functional interface in java.util.function.* this library will provide a corresponding "WithThrowable" interface.

For example, there is a Function<A, B> interface in java.util.function.* , so there the library provides a FunctionWithThrowable<A, B, E extends Throwable> interface in the library.

Cast Method

Each interface has a "cast...WithThrowable" method that just casts any lambda into the WithThrowable version.

files . stream () . filter ( PredicateWithThrowable . castPredicateWithThrowable ( file -> detectMimeType ( file ). equals ( "text/html" ))) // PredicateWithThrowable example . map ( FunctionWithThrowable . castFunctionWithThrowable ( IOUtils: : toString )) // FunctionWithThrowable example . forEach ( ConsumerWithThrowable . castConsumerWithThrowable ( content -> { // ConsumerWithThrowable example if ( content . isEmpty ()) { throw new Exception ( "empty content not allowed" ); } System . out . println ( "Found content: " + content ); });

Convert Method

If you use the WithThrowable interface in methods, you might sometimes want to pass in a original interface. Each WithThrowable interface implements a "as.. .WithThrowable()" method to aid the conversion.

public List < String > mapFilesWithIO ( List < File > files , FunctionWithThrowable < File , String , IOException > functionWithThrowable ) { return files . stream (). map ( functionWithThrowable ). collect ( Collectors . toList ()); } public List < String > mapFiles ( List < File > files , Function < File , String > function ) { return mapFilesWithIO ( files , FunctionWithThrowable . asFunctionWithThrowable ( function )); // contrived example to demonstrate conversion. }

Rethrowing SuppressedException

Instead of throwing a new instance of RuntimeException method, the WithThrowable interfaces will always wrap any caught exceptions as `SuppressedExceptions', so that you can catch them specifically. Here is three different ways you can unwrap exceptions.

public List < String > mapFilesWithIO ( List < File > files , FunctionWithThrowable < File , String , IOException > functionWithThrowable ) throws Throwable { try { return files . stream (). map ( functionWithThrowable ). collect ( Collectors . toList ()); } catch ( SuppressedException e ) { throw e . getCause (); } }

public List < String > mapFilesWithIO ( List < File > files , FunctionWithThrowable < File , String , IOException > functionWithThrowable ) throws IOException { try { return files . stream (). map ( functionWithThrowable ). collect ( Collectors . toList ()); } catch ( SuppressedException e ) { throw SuppressedException . unwrapSuppressedException ( e , IOException . class ). orElseThrow (() -> e ); } }

public List < String > mapFilesWithIO ( List < File > files , FunctionWithThrowable < File , String , IOException > functionWithThrowable ) throws IOException { return SuppressedException . unwrapSuppressedException (() -> { return files . stream (). map ( functionWithThrowable ). collect ( Collectors . toList ()); }, IOException . class ); }

It is worth noting that SuppressedExceptions.unwrapSuppressedExceptions is not made to deal with the case where you have multiple types of exceptions to rethrow.

Exception Handling

All the WithThrowable interfaces have some level of exception handling. All interfaces have the .onException() and .withLogging() methods. Interfaces that return a value has the .thatReturnsOptional() method, and interfaces with void methods have the .thatThrowsNothing() method.

onException()

The onException() method allows you define some custom exception handling code. eg

files . stream (). forEach ( ConsumerWithThrowable . castConsumerWithThrowable ( file -> { // do some io stuff with file. }). onException ( exception , args -> { // do something with the exception ( logging? ) and the args passed to consumer }) );

withLogging()

The withLogging() will log any exceptions to a slf4j logger.

files . stream (). forEach ( ConsumerWithThrowable . castConsumerWithThrowable ( file -> { // do some io stuff with file. }). withLogging ()) );

thatThrowsNothing()

The thatThrowsNothing() method will ignore any exceptions.

files . stream (). forEach ( ConsumerWithThrowable . castConsumerWithThrowable ( file -> { // do some io stuff with file. }). thatThrowsNothing ()) );

thatReturnsOptional()

The .thatReturnsOptional() will change the return type into an optional that will be empty if an exception was caught.

List < Optional < String >> readAttempts = files . stream () . map ( FunctionWithThrowable . castConsumerWithThrowable ( file -> { return IOUtils . toString ( file ); }). thatReturnsOptional ())) . collect ( Collectors . toList ());

You can chain these methods together:

files . stream (). forEach ( ConsumerWithThrowable . castConsumerWithThrowable ( file -> { // do some io stuff with file. }) . withLogging () . thatThrowsNothing ()) ); List < Optional < String >> readAttempts = files . stream () . map ( FunctionWithThrowable . castConsumerWithThrowable ( file -> IOUtils . toString ( file )) . onException ( exception , args -> System . err . println ( exception . getMessage ())) . thatReturnsOptional ()) ) . collect ( Collectors . toList ());

Here is a few example cases with FunctionWithThrowable