Why do Consumers accept lambdas with statement bodies but not expression bodies?

The following code surprisingly is compiling successfully:

Consumer<String> p = ""::equals;

This too:

p = s -> "".equals(s);

But this is fails with the error boolean cannot be converted to void as expected:

p = s -> true;

Modification of the second example with parenthesis also fails:

p = s -> ("".equals(s));

Is it a bug in Java compiler or is there a type inference rule I don't know about?


First, it's worth looking at what a Consumer<String> actually is. From the documentation:

Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.

So it's a function that accepts a String and returns nothing.

Consumer<String> p = ""::equals;

Compiles successfully because equals can take a String (and, indeed, any Object). The result of equals is just ignored.*

p = s -> "".equals(s);

This is exactly the same, but with different syntax. The compiler knows not to add an implicit return because a Consumer should not return a value. It would add an implicit return if the lambda was a Function<String, Boolean> though.

p = s -> true;

This takes a String (s) but because true is an expression and not a statement, the result cannot be ignored in the same way. The compiler has to add an implicit return because an expression can't exist on its own. Thus, this does have a return: a boolean. Therefore it's not a Consumer.**

p = s -> ("".equals(s));

Again, this is an expression, not a statement. Ignoring lambdas for a moment, you will see the line System.out.println("Hello"); will similarly fail to compile if you wrap it in parentheses.


*From the spec:

If the body of a lambda is a statement expression (that is, an expression that would be allowed to stand alone as a statement), it is compatible with a void-producing function type; any result is simply discarded.

**From the spec (thanks, Eugene):

A lambda expression is congruent with a [void-producing] function type if ... the lambda body is either a statement expression (§14.8) or a void-compatible block.


I think the other answers complicate the explanation by focusing on lambdas whereas their behavior in this case is similar to the behavior of manually implemented methods. This compiles:

new Consumer<String>() {
    @Override
    public void accept(final String s) {
        "".equals(s);
    }
}

whereas this does not:

new Consumer<String>() {
    @Override
    public void accept(final String s) {
        true;
    }
}

because "".equals(s) is a statement but true is not. A lambda expression for a functional interface returning void requires a statement so it follows the same rules as a method's body.

Note that in general lambda bodies don't follow exactly the same rules as method bodies - in particular, if a lambda whose body is an expression implements a method returning a value, it has an implicit return. So for example, x -> true would be a valid implementation of Function<Object, Boolean>, whereas true; is not a valid method body. But in this particular case functional interfaces and method bodies coincide.