How can I throw CHECKED exceptions from inside Java 8 streams?

136,883

Solution 1

This LambdaExceptionUtil helper class lets you use any checked exceptions in Java streams, like this:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Note Class::forName throws ClassNotFoundException, which is checked. The stream itself also throws ClassNotFoundException, and NOT some wrapping unchecked exception.

public final class LambdaExceptionUtil {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Many other examples on how to use it (after statically importing LambdaExceptionUtil):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }    

UPDATE as of Nov 2015 The code has been improved with the help of @PaoloC, please check his answer below and upvote it. He helped solve the last problem: Now the compiler will ask you to add throw clauses and everything's as if you could throw checked exceptions natively on Java 8 streams.


NOTE 1 The rethrow methods of the LambdaExceptionUtil class above may be used without fear, and are OK to use in any situation.


NOTE 2: The uncheck methods of the LambdaExceptionUtil class above are bonus methods, and may be safely removed them from the class if you don't want to use them. If you do used them, do it with care, and not before understanding the following use cases, advantages/disadvantages and limitations:

• You may use the uncheck methods if you are calling a method which literally can never throw the exception that it declares. For example: new String(byteArr, "UTF-8") throws UnsupportedEncodingException, but UTF-8 is guaranteed by the Java spec to always be present. Here, the throws declaration is a nuisance and any solution to silence it with minimal boilerplate is welcome: String text = uncheck(() -> new String(byteArr, "UTF-8"));

• You may use the uncheck methods if you are implementing a strict interface where you don't have the option for adding a throws declaration, and yet throwing an exception is entirely appropriate. Wrapping an exception just to gain the privilege of throwing it results in a stacktrace with spurious exceptions which contribute no information about what actually went wrong. A good example is Runnable.run(), which does not throw any checked exceptions.

• In any case, if you decide to use the uncheck methods, be aware of these 2 consequences of throwing CHECKED exceptions without a throws clause: 1) The calling-code won't be able to catch it by name (if you try, the compiler will say: Exception is never thrown in body of corresponding try statement). It will bubble and probably be caught in the main program loop by some "catch Exception" or "catch Throwable", which may be what you want anyway. 2) It violates the principle of least surprise: it will no longer be enough to catch RuntimeException to be able to guarantee catching all possible exceptions. For this reason, I believe this should not be done in framework code, but only in business code that you completely control.

  • References:

Solution 2

The simple answer to your question is: You can't, at least not directly. And it's not your fault. Oracle messed it up. They cling on the concept of checked exceptions, but inconsistently forgot to take care of checked exceptions when designing the functional interfaces, streams, lambda etc. That's all grist to the mill of experts like Robert C. Martin who call checked exceptions a failed experiment.

In my opinion, this is a huge bug in the API and a minor bug in the language specification.

The bug in the API is that it provides no facility for forwarding checked exceptions where this actually would make an awful lot of sense for functional programming. As I will demonstrate below, such a facility would've been easily possible.

The bug in the language specification is that it does not allow a type parameter to infer a list of types instead of a single type as long as the type parameter is only used in situations where a list of types is permissable (throws clause).

Our expectation as Java programmers is that the following code should compile:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

However, it gives:

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

The way in which the functional interfaces are defined currently prevents the Compiler from forwarding the exception - there is no declaration which would tell Stream.map() that if Function.apply() throws E, Stream.map() throws E as well.

What's missing is a declaration of a type parameter for passing through checked exceptions. The following code shows how such a pass-through type parameter actually could have been declared with the current syntax. Except for the special case in the marked line, which is a limit discussed below, this code compiles and behaves as expected.

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   
    
    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

In the case of throwSomeMore we would like to see IOException being missed, but it actually misses Exception.

This is not perfect because type inference seems to be looking for a single type, even in the case of exceptions. Because the type inference needs a single type, E needs to resolve to a common super of ClassNotFoundException and IOException, which is Exception.

A tweak to the definition of type inference is needed so that the compiler would look for multiple types if the type parameter is used where a list of types is permissible (throws clause). Then the exception type reported by the compiler would be as specific as the original throws declaration of the checked exceptions of the referenced method, not a single catch-all super type.

The bad news is that this means that Oracle messed it up. Certainly they won't break user-land code, but introducing exception type parameters to the existing functional interfaces would break compilation of all user-land code that uses these interfaces explicitly. They'll have to invent some new syntax sugar to fix this.

The even worse news is that this topic was already discussed by Brian Goetz in 2010 (https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java, http://mail.openjdk.java.net/pipermail/lambda-dev/2010-June/001484.html) but I'm informed that this investigation ultimately did not pan out, and that there is no current work at Oracle that I know of to mitigate the interactions between checked exceptions and lambdas.

Solution 3

You can!

Extending @marcg 's UtilException and adding throw E where necessary: this way, the compiler will ask you to add throw clauses and everything's as if you could throw checked exceptions natively on java 8's streams.

Instructions: just copy/paste LambdaExceptionUtil in your IDE and then use it as shown in the below LambdaExceptionUtilTest.

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try {
                consumer.accept(t);
            } catch (Exception exception) {
                throwActualException(exception);
            }
        };
    }

    /**
     * .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
     */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E  {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception exception) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

Some test to show usage and behaviour:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}

Solution 4

You can't do this safely. You can cheat, but then your program is broken and this will inevitably come back to bite someone (it should be you, but often our cheating blows up on someone else.)

Here's a slightly safer way to do it (but I still don't recommend this.)

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

Here, what you're doing is catching the exception in the lambda, throwing a signal out of the stream pipeline that indicates that the computation failed exceptionally, catching the signal, and acting on that signal to throw the underlying exception. The key is that you are always catching the synthetic exception, rather than allowing a checked exception to leak out without declaring that exception is thrown.

Solution 5

Just use any one of NoException (my project), jOOλ's Unchecked, throwing-lambdas, Throwable interfaces, or Faux Pas.

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));
Share:
136,883
MarcG
Author by

MarcG

Currently living in São Paulo and Rio de Janeiro - Brazil. I hold both Brazilian and European (Polish) passports. Aeronautical-Mechanical Engineer (Technological Institute of Aeronautics ITA, the "Brazilian MIT"). MBA (PUC-RJ) Software Architect and Developer. Previously C++, but now mostly Dart/Flutter (see https://pub.dev/publishers/glasberg.dev/packages), JavaScript (Typescript) and Java. Love TDD, BDD and Clean Code. Also very interested in Usability and Gamification. Excellent communication skills and technical writing. I program since I was 10 yo. Love travelling, astronomy, science and games (playing and making them). Links: https://github.com/marcglasberg https://www.linkedin.com/in/marcglasberg/ https://medium.com/flutter-community/https-medium-com-marcglasberg-async-redux-33ac5e27d5f6 https://medium.com/flutter-community/i18n-extension-flutter-b966f4c65df9 Patents: https://www.google.com/patents/US7917437 https://www.google.com/patents/US7596530 Fluent English and Portuguese, good Spanish, some French. Come visit Rio and rent my place in AirBnB: https://www.airbnb.com/rooms/13830632 [email protected]

Updated on July 12, 2022

Comments

  • MarcG
    MarcG almost 2 years

    How can I throw CHECKED exceptions from inside Java 8 streams/lambdas?

    In other words, I want to make code like this compile:

    public List<Class> getClasses() throws ClassNotFoundException {     
    
        List<Class> classes = 
            Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(className -> Class.forName(className))
                  .collect(Collectors.toList());                  
        return classes;
        }
    

    This code does not compile, since the Class.forName() method above throws ClassNotFoundException, which is checked.

    Please note I do NOT want to wrap the checked exception inside a runtime exception and throw the wrapped unchecked exception instead. I want to throw the checked exception itself, and without adding ugly try/catches to the stream.