Why can't I throw an exception in a Java 8 lambda expression?

62,349

Solution 1

You are not allowed to throw checked exceptions because the accept(T t, U u) method in the java.util.function.BiConsumer<T, U> interface doesn't declare any exceptions in its throws clause. And, as you know, Map#forEach takes such a type.

public interface Map<K, V> {

    default void forEach(BiConsumer<? super K, ? super V> action) { ... }

}                        |
                         |
                         V
@FunctionalInterface
public interface BiConsumer<T, U> {

    void accept(T t, U u); // <-- does throw nothing

}

That is true when we are talking about checked exceptions. But you still can throw an unchecked exception (e.g. a java.lang.IllegalArgumentException):

new HashMap<String, String>()
    .forEach((a, b) -> { throw new IllegalArgumentException(); });

Solution 2

You can throw exceptions in lambdas.

A lambda is allowed to throw the same exceptions as the functional interface implemented by the lambda.

If the method of the functional interface doesn't have a throws clause, the lambda can't throw CheckedExceptions. (You still can throw RuntimeExceptions).


In your particular case, Map.forEach uses a BiConsumer as a parameter, BiConsumer is defined as:

public interface BiConsumer<T, U> {
    void accept(T t, U u);
}

A lambda expression for this functional interface can't throw CheckedExceptions.


The methods in the functional interfaces defined in java.util.function package don't throw exceptions, but you can use other functional interfaces or create your own to be able to throw exceptions, i.e. given this interface:

public interface MyFunctionalInterface<T> {
    void accept(T t) throws Exception;
}

The following code would be legal:

MyFunctionalInterface<String> f = (s)->throw new Exception("Exception thrown in lambda"); 
Share:
62,349

Related videos on Youtube

dokaspar
Author by

dokaspar

Updated on June 17, 2020

Comments

  • dokaspar
    dokaspar almost 4 years

    I upgraded to Java 8 and tried to replace a simple iteration through a Map with a new lamdba expression. The loop searches for null values and throws an exception if one is found. The old Java 7 code looks like this:

    for (Map.Entry<String, String> entry : myMap.entrySet()) {
        if(entry.getValue() == null) {
            throw new MyException("Key '" + entry.getKey() + "' not found!");
        }
    }
    

    And my attempt to convert this to Java 8 looks like this:

    myMap.forEach((k,v) -> {
        if(v == null) {
            // OK
            System.out.println("Key '" + k+ "' not found!");
    
            // NOK! Unhandled exception type!
            throw new MyException("Key '" + k + "' not found!");
        }
    });
    

    Can anyone explain why the throw statement not allowed here and how this could be corrected?

    Eclipse's quick-fix suggestion does not look right to me... it simply surrounds the throw statement with a try-catch block:

    myMap.forEach((k,v) -> {
        if(v == null) {
            try {
                throw new MyException("Key '" + k + "' not found!");
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
    
    • Brian Goetz
      Brian Goetz almost 8 years
      There's a very simple answer: lambda expressions are a concise means of implementing a functional interface. Just like any other way to implement an interface, you have to conform to the interface contract -- which means you can only throw the exceptions allowed by the target type. There's no magic here.
  • micker
    micker almost 8 years
    I pretty much agree with this answer. The confusing thing about the javadocs is the statement "Exceptions thrown by the action are relayed to the caller.". As a side note, throwing exceptions within a lambda is certainly allowed, just not in this particular case.
  • Ti Strga
    Ti Strga over 6 years
    @micker It may be worth pointing out: that statement is only attached to the forEach default member method of the Iterable<T> interface. The primary Stream/Consumer forEach doesn't document anything about checked exception behavior (although some conclusions can be drawn from the function signatures).
  • hd84335
    hd84335 almost 4 years
    @andrew-tobilko, thanks for your answer! I wonder if we have a similar case in java Optional.ifPresentOrElse where we can't also throw any checked exception.