48

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();
        }
    }
});
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
dokaspar
  • 8,186
  • 14
  • 70
  • 98
  • 14
    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. – Brian Goetz Jun 09 '16 at 15:08

2 Answers2

68

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(); });
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • 4
    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. – micker Jun 09 '16 at 13:17
  • @micker It may be worth pointing out: that statement is only attached to the `forEach` default member method of the `Iterable` interface. The primary Stream/Consumer `forEach` doesn't document anything about checked exception behavior (although some conclusions can be drawn from the function signatures). – Ti Strga Jan 16 '18 at 17:38
  • @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. – hd84335 Jul 16 '20 at 23:05
30

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"); 
David SN
  • 3,389
  • 1
  • 17
  • 21